From 2221d5b5d05fb78f5d8bb05e349dbe22b906de9c Mon Sep 17 00:00:00 2001 From: Emmanuel Leblond Date: Fri, 18 Oct 2024 20:50:00 +0200 Subject: [PATCH 1/8] Add sanity check in testbed template builder to fail when overwritting stuff --- libparsec/crates/testbed/src/template/build.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libparsec/crates/testbed/src/template/build.rs b/libparsec/crates/testbed/src/template/build.rs index 61cdc5b4f6d..74724238e73 100644 --- a/libparsec/crates/testbed/src/template/build.rs +++ b/libparsec/crates/testbed/src/template/build.rs @@ -264,7 +264,7 @@ pub(super) fn get_stuff( }); value_any.downcast_ref::().unwrap_or_else(|| { panic!( - "Key `{}` is among the stuff, but you got it type wrong :'( (caller: {})", + "Key `{}` is among the stuff, but you got its type wrong :'( (caller: {})", key, caller ); }) @@ -310,6 +310,11 @@ impl TestbedTemplateBuilder { obj: &(impl std::any::Any + Clone + Send + Sync), ) { let boxed = Box::new(obj.to_owned()); + assert!( + self.stuff.iter().all(|(k, _)| *k != key), + "Key `{}` is already part of stuff", + key + ); // It's no big deal to leak the data here: the template is kept until the end // of the program anyway (and the amount of leak is negligeable). // On the other hand it allows to provide the stuff as `&'static Foo` which From c70a0de3650b4ef1922709f07fdb9ffa86b370e0 Mon Sep 17 00:00:00 2001 From: Emmanuel Leblond Date: Fri, 18 Oct 2024 20:50:53 +0200 Subject: [PATCH 2/8] Add libparsec_client_connection testbed send hook helpers to more easily mock RPC requests --- libparsec/crates/client_connection/src/lib.rs | 2 + .../src/testbed_send_hook_helpers.rs | 244 ++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 libparsec/crates/client_connection/src/testbed_send_hook_helpers.rs diff --git a/libparsec/crates/client_connection/src/lib.rs b/libparsec/crates/client_connection/src/lib.rs index 9ecc933cd71..6f29480643a 100644 --- a/libparsec/crates/client_connection/src/lib.rs +++ b/libparsec/crates/client_connection/src/lib.rs @@ -6,6 +6,8 @@ mod error; mod invited_cmds; #[cfg(feature = "test-with-testbed")] mod testbed; +#[cfg(feature = "test-with-testbed")] +mod testbed_send_hook_helpers; pub use anonymous_cmds::AnonymousCmds; pub use authenticated_cmds::{ diff --git a/libparsec/crates/client_connection/src/testbed_send_hook_helpers.rs b/libparsec/crates/client_connection/src/testbed_send_hook_helpers.rs new file mode 100644 index 00000000000..fbdc2867ff9 --- /dev/null +++ b/libparsec/crates/client_connection/src/testbed_send_hook_helpers.rs @@ -0,0 +1,244 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +/// Register a `vlob_read_batch` RPC call. +/// +/// Notes: +/// - Use the `allowed: [...]` parameter in case the order the client will query multiple +/// entries is not guaranteed (typically the case when iterating over children). +/// - No access control is done ! So only use this for a user that has access to the realm. +/// - Non-existence of `$realm_id` or `$entry_id` is handled fine. +#[macro_export] +macro_rules! test_send_hook_vlob_read_batch { + (__internal, $strict: expr, $env: expr, $at:expr, $realm_id: expr, $($entry_id: expr),* $(,)?) => { + { + let env: &TestbedEnv = $env; + let realm_id: VlobID = $realm_id; + let at: Option = $at; + + let last_common_certificate_timestamp = env.get_last_common_certificate_timestamp(); + let last_realm_certificate_timestamp = env.get_last_realm_certificate_timestamp(realm_id); + let last_realm_key_index = env.get_last_realm_keys_bundle_index(realm_id); + + let mk_rev_iter_events = || env.template.events.iter().rev() + .filter(|e| match at { + None => true, + Some(at) => match e { + TestbedEvent::CreateOrUpdateUserManifestVlob(e) => e.manifest.timestamp <= at, + TestbedEvent::CreateOrUpdateFileManifestVlob(e) => e.manifest.timestamp <= at, + TestbedEvent::CreateOrUpdateFolderManifestVlob(e) => e.manifest.timestamp <= at, + TestbedEvent::CreateOrUpdateOpaqueVlob(e) => e.timestamp <= at, + _ => false, + } + }); + + let mut items = vec![]; + let mut expected_vlobs_param = vec![]; + $( + let entry_id: VlobID = $entry_id; + expected_vlobs_param.push(entry_id); + let maybe_found = mk_rev_iter_events() + .find_map(|e| match e { + TestbedEvent::CreateOrUpdateUserManifestVlob(e) + if e.manifest.id == realm_id && e.manifest.id == entry_id => { + Some(( + 0, + e.manifest.author, + e.manifest.version, + e.manifest.timestamp, + e.encrypted(&env.template) + )) + } + TestbedEvent::CreateOrUpdateFileManifestVlob(e) + if e.realm == realm_id && e.manifest.id == entry_id => + { + Some(( + e.key_index, + e.manifest.author, + e.manifest.version, + e.manifest.timestamp, + e.encrypted(&env.template) + )) + } + TestbedEvent::CreateOrUpdateFolderManifestVlob(e) + if e.realm == realm_id && e.manifest.id == entry_id => + { + Some(( + e.key_index, + e.manifest.author, + e.manifest.version, + e.manifest.timestamp, + e.encrypted(&env.template) + )) + } + TestbedEvent::CreateOrUpdateOpaqueVlob(e) + if e.realm == realm_id && e.vlob_id == entry_id => { + Some(( + e.key_index, + e.author, + e.version, + e.timestamp, + e.encrypted.clone(), + )) + } + _ => None, + }); + + if let Some(( + manifest_encrypted_key_index, + manifest_author, + manifest_version, + manifest_timestamp, + manifest_encrypted, + )) = maybe_found { + items.push( + ( + entry_id, + manifest_encrypted_key_index, + manifest_author, + manifest_version, + manifest_timestamp, + manifest_encrypted, + ) + ); + } + )* + + let realm_exists = if items.is_empty() { + // If we didn't find any item, it might be because the realm doesn't + // exist yet, in which case we should return an error + env.template.events.iter().find_map(|e| match (e, at) { + (TestbedEvent::NewRealm(e), Some(at)) if e.timestamp > at => Some(false), + (TestbedEvent::NewRealm(e), _) if e.realm_id == realm_id => Some(true), + _ => None, + }).unwrap_or(false) + } else { + true + }; + let mut rep = if realm_exists { + $crate::protocol::authenticated_cmds::latest::vlob_read_batch::Rep::Ok { + items, + needed_common_certificate_timestamp: last_common_certificate_timestamp, + needed_realm_certificate_timestamp: last_realm_certificate_timestamp, + } + } else { + $crate::protocol::authenticated_cmds::latest::vlob_read_batch::Rep::RealmNotFound + }; + + move |req: $crate::protocol::authenticated_cmds::latest::vlob_read_batch::Req| { + p_assert_eq!(req.realm_id, realm_id); + p_assert_eq!(req.at, at); + if $strict { + // In strict mode, the client should have requested all the entries. + p_assert_eq!(req.vlobs, expected_vlobs_param); + } else { + // In non-strict mode, the client can have requested a subset of the entries. + if let $crate::protocol::authenticated_cmds::latest::vlob_read_batch::Rep::Ok{items, ..} = &mut rep { + items.retain(|(entry_id, _, _, _, _, _)| req.vlobs.contains(entry_id)); + } + } + rep + } + } + }; + + ($env: expr, $realm_id: expr, $($entry_id: expr),* $(,)?) => { + test_send_hook_vlob_read_batch!(__internal, true, $env, None, $realm_id, $($entry_id),*) + }; + ($env: expr, at: $at: expr, $realm_id: expr, $($entry_id: expr),* $(,)?) => { + test_send_hook_vlob_read_batch!(__internal, true, $env, Some($at), $realm_id, $($entry_id),*) + }; + ($env: expr, $realm_id: expr, allowed: [$($entry_id: expr),*] $(,)?) => { + test_send_hook_vlob_read_batch!(__internal, false, $env, None, $realm_id, $($entry_id),*) + }; + ($env: expr, at: $at: expr, $realm_id: expr, allowed: [$($entry_id: expr),*] $(,)?) => { + test_send_hook_vlob_read_batch!(__internal, false, $env, Some($at), $realm_id, $($entry_id),*) + }; +} + +/// Register a `realm_get_keys_bundle` RPC call. +/// +/// Notes: +/// - No access control is done ! So only use this for a user that has access to the realm. +#[macro_export] +macro_rules! test_send_hook_realm_get_keys_bundle { + (__internal, $env: expr, $author: expr, $realm_id: expr, $key_index: expr) => {{ + let env: &TestbedEnv = $env; + let realm_id: VlobID = $realm_id; + let author: UserID = $author; + + let (key_index, keys_bundle, keys_bundle_access) = match $key_index { + None => { + let keys_bundle = env.get_last_realm_keys_bundle(realm_id); + let key_index = env.get_last_realm_keys_bundle_index(realm_id); + let keys_bundle_access = + env.get_last_realm_keys_bundle_access_for(realm_id, author); + (key_index, keys_bundle, keys_bundle_access) + } + Some(key_index) => { + let keys_bundle = env.get_realm_keys_bundle(realm_id, key_index); + let keys_bundle_access = + env.get_keys_bundle_access_for(realm_id, author, key_index); + (key_index, keys_bundle, keys_bundle_access) + } + }; + + move |req: $crate::protocol::authenticated_cmds::latest::realm_get_keys_bundle::Req| { + p_assert_eq!(req.realm_id, realm_id); + p_assert_eq!(req.key_index, key_index); + $crate::protocol::authenticated_cmds::latest::realm_get_keys_bundle::Rep::Ok { + keys_bundle, + keys_bundle_access, + } + } + }}; + + ($env: expr, $author: expr, $realm_id: expr) => { + test_send_hook_realm_get_keys_bundle!(__internal, $env, $author, $realm_id, None); + }; + ($env: expr, $author: expr, $realm_id: expr, $key_index: expr) => { + test_send_hook_realm_get_keys_bundle!( + __internal, + $env, + $author, + $realm_id, + Some($key_index) + ); + }; +} + +/// Register a `block_read` RPC call. +/// +/// Notes: +/// - No access control is done ! So only use this for a user that has access to the realm. +#[macro_export] +macro_rules! test_send_hook_block_read { + ($env: expr, $realm_id: expr, $block_id: expr) => {{ + let env: &TestbedEnv = $env; + let realm_id: VlobID = $realm_id; + let block_id: BlockID = $block_id; + + let last_realm_certificate_timestamp = env.get_last_realm_certificate_timestamp(realm_id); + let fetch_block_rep = env + .template + .events + .iter() + .rev() + .find_map(|e| match e { + TestbedEvent::CreateBlock(e) if e.block_id == block_id => { + let rep = $crate::protocol::authenticated_cmds::latest::block_read::Rep::Ok { + needed_realm_certificate_timestamp: last_realm_certificate_timestamp, + key_index: e.key_index, + block: e.encrypted(&env.template), + }; + Some(rep) + } + _ => None, + }) + .unwrap(); + + move |req: $crate::protocol::authenticated_cmds::latest::block_read::Req| { + p_assert_eq!(req.block_id, block_id); + fetch_block_rep + } + }}; +} From 908ae9cb35cf2005fccbd008e6186104bff31960 Mon Sep 17 00:00:00 2001 From: Emmanuel Leblond Date: Fri, 18 Oct 2024 20:45:35 +0200 Subject: [PATCH 3/8] Create `workspace_history` testbed template --- libparsec/crates/testbed/src/lib.rs | 8 +- .../crates/testbed/src/template/build.rs | 4 + libparsec/crates/testbed/src/templates/mod.rs | 1 + .../src/templates/workspace_history.rs | 411 ++++++++++++++++++ 4 files changed, 423 insertions(+), 1 deletion(-) create mode 100644 libparsec/crates/testbed/src/templates/workspace_history.rs diff --git a/libparsec/crates/testbed/src/lib.rs b/libparsec/crates/testbed/src/lib.rs index dc254373808..5b9ec99b41c 100644 --- a/libparsec/crates/testbed/src/lib.rs +++ b/libparsec/crates/testbed/src/lib.rs @@ -23,11 +23,17 @@ enum TemplateState { // Templates are generated only once, then copied for fast initialization of testbed envs // On top of that we generated them lazily to further improve speed of single-test runs // given cargo-nextest relies on that even when running multiple tests. -static TESTBED_TEMPLATES: [(&str, Mutex); 4] = [ +static TESTBED_TEMPLATES: [(&str, Mutex); 5] = [ ( "empty", Mutex::new(TemplateState::NotGenerated(templates::empty::generate)), ), + ( + "workspace_history", + Mutex::new(TemplateState::NotGenerated( + templates::workspace_history::generate, + )), + ), ( "minimal", Mutex::new(TemplateState::NotGenerated(templates::minimal::generate)), diff --git a/libparsec/crates/testbed/src/template/build.rs b/libparsec/crates/testbed/src/template/build.rs index 74724238e73..588bd6a4d5f 100644 --- a/libparsec/crates/testbed/src/template/build.rs +++ b/libparsec/crates/testbed/src/template/build.rs @@ -180,6 +180,10 @@ impl TestbedTemplateBuilderCounters { self.current_timestamp += Duration::try_days(1).expect("Invalid duration"); self.current_timestamp } + pub fn set_current_timestamp(&mut self, timestamp: DateTime) { + assert!(timestamp > self.current_timestamp); + self.current_timestamp = timestamp; + } pub fn current_timestamp(&self) -> DateTime { self.current_timestamp } diff --git a/libparsec/crates/testbed/src/templates/mod.rs b/libparsec/crates/testbed/src/templates/mod.rs index 22859115983..59478b66ab4 100644 --- a/libparsec/crates/testbed/src/templates/mod.rs +++ b/libparsec/crates/testbed/src/templates/mod.rs @@ -4,3 +4,4 @@ pub(crate) mod coolorg; pub(crate) mod empty; pub(crate) mod minimal; pub(crate) mod minimal_client_ready; +pub(crate) mod workspace_history; diff --git a/libparsec/crates/testbed/src/templates/workspace_history.rs b/libparsec/crates/testbed/src/templates/workspace_history.rs new file mode 100644 index 00000000000..f0cef050182 --- /dev/null +++ b/libparsec/crates/testbed/src/templates/workspace_history.rs @@ -0,0 +1,411 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use std::{collections::HashMap, sync::Arc}; + +use libparsec_types::prelude::*; + +use crate::TestbedTemplate; + +/// WorkspaceHistory contains: +/// - 3 users: `alice` (admin), `bob` (regular) and `mallory` (regular) +/// - 3 devices: `alice@dev1`, `bob@dev1` and `mallory@dev1` (every device has its local +/// storage up-to-date regarding the certificates) +/// - On 2001-01-01, Alice creates and bootstraps workspace `wksp1` +/// - On 2001-01-02, Alice uploads workspace manifest in v1 (empty) +/// - On 2001-01-03T01:00:00, Alice uploads file manifest `bar.txt` in v1 (containing "Hello v1") +/// - On 2001-01-03T02:00:00, Alice uploads folder manifest `foo` in v1 (empty) +/// - On 2001-01-03T03:00:00, Alice uploads workspace manifest in v2 (containing `foo` and `bar.txt`) +/// - On 2001-01-04, Alice gives Bob access to the workspace (as OWNER) +/// - On 2001-01-05, Alice gives Mallory access to the workspace (as CONTRIBUTOR) +/// - On 2001-01-06, Bob removes access to the workspace from Alice +/// - On 2001-01-07, Mallory uploads file manifest `bar.txt` in v2 (containing "Hello v2 world", stored on 2 blocks) +/// - On 2001-01-08T01:00:00, Mallory uploads folder manifest `foo` in v2 (containing `spam` and `egg.txt`) +/// - On 2001-01-08T02:00:00, Mallory uploads file manifest `foo/egg.txt` in v1 (empty file) +/// - On 2001-01-08T03:00:00, Mallory uploads folder manifest `foo/spam` in v1 (empty) +/// - On 2001-01-09, Bob uploads workspace manifest in v3 with renames `foo`->`foo2` and `bar.txt`->`bar2.txt` +/// - On 2001-01-10, Bob uploads file manifest `bar.txt` (now named `bar2.txt`) in v3 with content "Hello v3" +/// - On 2001-01-11, Bob uploads file manifest `foo/egg.txt` in v2 with content "v2" +/// - On 2001-01-12, Bob uploads folder manifest `foo` (now named `foo2`) in v3 with rename `egg.txt`->`egg2.txt` and removed `spam` +/// - On 2001-01-30, Bob gives back access to the workspace to Alice (as READER) +/// +/// In the end we have the following per-entry history: +/// - `/` +/// - 2001-01-02: v1 from Alice, content: [] +/// - 2001-01-03T03:00:00: v2 from Alice, content: ["foo", "bar.txt"] +/// - 2001-01-09: v3 from Bob, content: ["foo2", "bar2.txt"] +/// - `bar.txt`: +/// - 2001-01-03T01:00:00: v1 from Alice, content: "Hello v1" +/// - 2001-01-07: v2 from Alice, content: "Hello v2 world" +/// - 2001-01-10: v3 from Bob, content "Hello v3" +/// - `foo`: +/// - 2001-01-03T02:00:00: v1 from Alice, content: [] +/// - 2001-01-08T01:00:00: v2 from Mallory, content ["spam", "egg.txt"], +/// with children not existing themselves yet ! +/// - 2001-01-12: v3 from Bob, content ["egg2.txt"] +/// - `foo/egg.txt`: +/// - 2001-01-08T02:00:00: v1 from Mallory, content "" +/// - 2001-01-11: v2 from Bob, content "v2" +/// - `foo/spam`: +/// - 2001-01-08T03:00:00: v1 from Mallory, content: [] +pub(crate) fn generate() -> Arc { + let mut builder = TestbedTemplate::from_builder("workspace_history"); + + // `Hello vX` is 8 bytes long, so we can store is on a single block + let block_size: Blocksize = 8.try_into().unwrap(); + + // 1) Create user & devices + + builder.bootstrap_organization("alice"); // alice@dev1 + builder + .new_user("bob") + .with_initial_profile(UserProfile::Standard); // bob@dev1 + builder + .new_user("mallory") + .with_initial_profile(UserProfile::Outsider); // mallory@dev1 + + // 2) Create the workspace and its history + + let wksp1_bar_txt_id = builder.counters.next_entry_id(); + let wksp1_foo_id = builder.counters.next_entry_id(); + let wksp1_foo_egg_txt_id = builder.counters.next_entry_id(); + let wksp1_foo_spam_id = builder.counters.next_entry_id(); + builder.store_stuff("wksp1_bar_txt_id", &wksp1_bar_txt_id); + builder.store_stuff("wksp1_foo_id", &wksp1_foo_id); + builder.store_stuff("wksp1_foo_egg_txt_id", &wksp1_foo_egg_txt_id); + builder.store_stuff("wksp1_foo_spam_id", &wksp1_foo_spam_id); + + // On 2001-01-01, Alice creates and bootstraps workspace `wksp1` + let wksp1_created_timestamp = "2001-01-01T00:00:00Z".parse::().unwrap(); + let wksp1_bootstrapped_timestamp = "2001-01-01T00:00:02Z".parse::().unwrap(); + + let wksp1_id = builder + .new_realm("alice") + .customize(|e| e.timestamp = wksp1_created_timestamp) + .map(|e| e.realm_id); + builder + .rotate_key_realm(wksp1_id) + .customize(|e| e.timestamp = "2001-01-01T00:00:01Z".parse().unwrap()); + let wksp1_name = builder + .rename_realm(wksp1_id, "wksp1") + .customize(|e| e.timestamp = wksp1_bootstrapped_timestamp) + .map(|e| e.name.clone()); + + builder.store_stuff("wksp1_id", &wksp1_id); + builder.store_stuff("wksp1_name", &wksp1_name); + builder.store_stuff("wksp1_created_timestamp", &wksp1_created_timestamp); + builder.store_stuff( + "wksp1_bootstrapped_timestamp", + &wksp1_bootstrapped_timestamp, + ); + + // On 2001-01-02, Alice uploads workspace manifest in v1 (empty) + + let wksp1_v1_timestamp = "2001-01-02T00:00:00Z".parse::().unwrap(); + + builder + .create_or_update_workspace_manifest_vlob("alice@dev1", wksp1_id) + .customize(|e| Arc::make_mut(&mut e.manifest).timestamp = wksp1_v1_timestamp); + + builder.store_stuff("wksp1_v1_timestamp", &wksp1_v1_timestamp); + + // On 2001-01-03T01:00:00, Alice uploads file manifest `bar.txt` in v1 (containing "Hello v1") + + let wksp1_bar_txt_v1_timestamp = "2001-01-03T01:00:00Z".parse::().unwrap(); + let bar_txt_v1_content = b"Hello v1"; + + let bar_txt_v1_block_access = builder + .create_block("alice@dev1", wksp1_id, bar_txt_v1_content.as_ref()) + .as_block_access(0); + builder.store_stuff("wksp1_bar_txt_v1_block_access", &bar_txt_v1_block_access); + + builder + .create_or_update_file_manifest_vlob( + "alice@dev1", + wksp1_id, + Some(wksp1_bar_txt_id), + wksp1_id, + ) + .customize(|e| { + let manifest = Arc::make_mut(&mut e.manifest); + manifest.blocksize = block_size; + manifest.timestamp = wksp1_bar_txt_v1_timestamp; + manifest.size = bar_txt_v1_block_access.size.get(); + manifest.blocks = vec![bar_txt_v1_block_access]; + }) + .map(|e| e.manifest.id); + + builder.store_stuff("wksp1_bar_txt_v1_timestamp", &wksp1_bar_txt_v1_timestamp); + + // On 2001-01-03T02:00:00, Alice uploads folder manifest `foo` in v1 (empty) + + let wksp1_foo_v1_timestamp = "2001-01-03T02:00:00Z".parse::().unwrap(); + + builder + .create_or_update_folder_manifest_vlob("alice@dev1", wksp1_id, Some(wksp1_foo_id), wksp1_id) + .customize(|e| { + let manifest = Arc::make_mut(&mut e.manifest); + manifest.timestamp = wksp1_foo_v1_timestamp; + }) + .map(|e| e.manifest.id); + + builder.store_stuff("wksp1_foo_v1_timestamp", &wksp1_foo_v1_timestamp); + + // On 2001-01-03T03:00:00, Alice uploads workspace manifest in v2 (containing `foo` and `bar.txt`) + + let wksp1_v2_timestamp = "2001-01-03T03:00:00Z".parse::().unwrap(); + + builder + .create_or_update_workspace_manifest_vlob("alice@dev1", wksp1_id) + .customize(|e| { + let manifest = Arc::make_mut(&mut e.manifest); + manifest.timestamp = wksp1_v2_timestamp; + manifest.children = HashMap::from_iter([ + ("foo".parse().unwrap(), wksp1_foo_id), + ("bar.txt".parse().unwrap(), wksp1_bar_txt_id), + ]); + }); + + builder.store_stuff("wksp1_v2_timestamp", &wksp1_v2_timestamp); + + // On 2001-01-04, Alice gives Bob access to the workspace (as OWNER) + + builder + .share_realm(wksp1_id, "bob", Some(RealmRole::Owner)) + .customize(|e| e.timestamp = "2001-01-04T00:00:00Z".parse().unwrap()); + + // On 2001-01-05, Alice gives Mallory access to the workspace (as CONTRIBUTOR) + + builder + .share_realm(wksp1_id, "mallory", Some(RealmRole::Contributor)) + .customize(|e| e.timestamp = "2001-01-05T00:00:00Z".parse().unwrap()); + + // On 2001-01-06, Bob removes access to the workspace from Alice + + builder + .share_realm(wksp1_id, "alice", None) + .customize(|e| e.timestamp = "2001-01-06T00:00:00Z".parse().unwrap()); + + // On 2001-01-07, Mallory uploads file manifest `bar.txt` in v2 (containing "Hello v2 world", stored on 2 blocks) + + let wksp1_bar_txt_v2_timestamp = "2001-01-07T00:00:00Z".parse::().unwrap(); + let bar_txt_v2_content = b"Hello v2 world"; + + let bar_txt_v2_block1_access = builder + .create_block( + "mallory@dev1", + wksp1_id, + bar_txt_v2_content.as_ref()[..*block_size as _].as_ref(), + ) + .as_block_access(0); + let bar_txt_v2_block2_access = builder + .create_block( + "mallory@dev1", + wksp1_id, + bar_txt_v2_content.as_ref()[*block_size as _..].as_ref(), + ) + .as_block_access(*block_size); + assert_eq!(bar_txt_v2_block2_access.offset, 8); + assert_eq!(bar_txt_v2_block2_access.size.get(), 6); + assert_eq!(bar_txt_v2_block1_access.offset, 0); + assert_eq!(bar_txt_v2_block1_access.size.get(), 8); + builder.store_stuff("wksp1_bar_txt_v2_block1_access", &bar_txt_v2_block1_access); + builder.store_stuff("wksp1_bar_txt_v2_block2_access", &bar_txt_v2_block2_access); + + builder + .create_or_update_file_manifest_vlob( + "mallory@dev1", + wksp1_id, + Some(wksp1_bar_txt_id), + wksp1_id, + ) + .customize(|e| { + let manifest = Arc::make_mut(&mut e.manifest); + manifest.blocksize = block_size; + manifest.timestamp = wksp1_bar_txt_v2_timestamp; + manifest.size = bar_txt_v2_content.len() as _; + manifest.blocks = vec![bar_txt_v2_block1_access, bar_txt_v2_block2_access]; + }) + .map(|e| e.manifest.id); + + builder.store_stuff("wksp1_bar_txt_v2_timestamp", &wksp1_bar_txt_v2_timestamp); + + // On 2001-01-08T01:00:00, Mallory uploads folder manifest `foo` in v2 (containing `spam` and `egg.txt`) + + let wksp1_foo_v2_timestamp = "2001-01-08T01:00:00Z".parse::().unwrap(); + + builder + .create_or_update_folder_manifest_vlob("mallory@dev1", wksp1_id, Some(wksp1_foo_id), None) + .customize(|e| { + let manifest = Arc::make_mut(&mut e.manifest); + manifest.timestamp = wksp1_foo_v2_timestamp; + manifest.children = HashMap::from_iter([ + ("spam".parse().unwrap(), wksp1_foo_spam_id), + ("egg.txt".parse().unwrap(), wksp1_foo_egg_txt_id), + ]); + }) + .map(|e| e.manifest.id); + + builder.store_stuff("wksp1_foo_v2_timestamp", &wksp1_foo_v2_timestamp); + + // On 2001-01-08T02:00:00, Mallory uploads file manifest `foo/egg.txt` in v1 (empty file) + + let wksp1_foo_egg_txt_v1_timestamp = "2001-01-08T02:00:00Z".parse::().unwrap(); + + builder + .create_or_update_file_manifest_vlob( + "mallory@dev1", + wksp1_id, + Some(wksp1_foo_egg_txt_id), + wksp1_foo_id, + ) + .customize(|e| Arc::make_mut(&mut e.manifest).timestamp = wksp1_foo_egg_txt_v1_timestamp) + .map(|e| e.manifest.id); + + builder.store_stuff( + "wksp1_foo_egg_txt_v1_timestamp", + &wksp1_foo_egg_txt_v1_timestamp, + ); + + // On 2001-01-08T03:00:00, Mallory uploads folder manifest `foo/spam` in v1 (empty) + + let wksp1_foo_spam_v1_timestamp = "2001-01-08T03:00:00Z".parse::().unwrap(); + + builder + .create_or_update_folder_manifest_vlob( + "mallory@dev1", + wksp1_id, + Some(wksp1_foo_spam_id), + wksp1_foo_id, + ) + .customize(|e| Arc::make_mut(&mut e.manifest).timestamp = wksp1_foo_spam_v1_timestamp) + .map(|e| e.manifest.id); + + builder.store_stuff("wksp1_foo_spam_v1_timestamp", &wksp1_foo_spam_v1_timestamp); + // Alias for this timestamp to simply know when the parent manifest children become valid + builder.store_stuff( + "wksp1_foo_v2_children_available_timestamp", + &wksp1_foo_spam_v1_timestamp, + ); + + // On 2001-01-09, Bob uploads workspace manifest in v3 with renames `foo`->`foo2` and `bar.txt`->`bar2.txt` + + let wksp1_v3_timestamp = "2001-01-09T00:00:00Z".parse::().unwrap(); + + builder + .create_or_update_workspace_manifest_vlob("bob@dev1", wksp1_id) + .customize(|e| { + let manifest = Arc::make_mut(&mut e.manifest); + manifest.timestamp = wksp1_v3_timestamp; + manifest.children = HashMap::from_iter([ + ("foo2".parse().unwrap(), wksp1_foo_id), + ("bar2.txt".parse().unwrap(), wksp1_bar_txt_id), + ]); + }); + + builder.store_stuff("wksp1_v3_timestamp", &wksp1_v3_timestamp); + + // On 2001-01-10, Bob uploads file manifest `bar.txt` (now named `bar2.txt`) in v3 with content "Hello v3" + + let wksp1_bar_txt_v3_timestamp = "2001-01-10T00:00:00Z".parse::().unwrap(); + let bar_txt_v3_content = b"Hello v3"; + + let bar_txt_v3_block_access = builder + .create_block("bob@dev1", wksp1_id, bar_txt_v3_content.as_ref()) + .as_block_access(0); + builder.store_stuff("wksp1_bar_txt_v3_block_access", &bar_txt_v3_block_access); + + builder + .create_or_update_file_manifest_vlob("bob@dev1", wksp1_id, Some(wksp1_bar_txt_id), wksp1_id) + .customize(|e| { + let manifest = Arc::make_mut(&mut e.manifest); + manifest.blocksize = block_size; + manifest.timestamp = wksp1_bar_txt_v3_timestamp; + manifest.size = bar_txt_v3_content.len() as _; + manifest.blocks = vec![bar_txt_v3_block_access]; + }) + .map(|e| e.manifest.id); + + builder.store_stuff("wksp1_bar_txt_v3_timestamp", &wksp1_bar_txt_v3_timestamp); + + // On 2001-01-11, Bob uploads file manifest `foo/egg.txt` in v2 with content "v2" + + let wksp1_foo_egg_txt_v2_timestamp = "2001-01-11T00:00:00Z".parse::().unwrap(); + let foo_egg_txt_v2_content = b"v2"; + + let foo_egg_txt_v2_block_access = builder + .create_block("bob@dev1", wksp1_id, foo_egg_txt_v2_content.as_ref()) + .as_block_access(0); + builder.store_stuff( + "wksp1_foo_egg_txt_v2_block_access", + &foo_egg_txt_v2_block_access, + ); + + builder + .create_or_update_file_manifest_vlob("bob@dev1", wksp1_id, Some(wksp1_bar_txt_id), wksp1_id) + .customize(|e| { + let manifest = Arc::make_mut(&mut e.manifest); + manifest.blocksize = block_size; + manifest.timestamp = wksp1_foo_egg_txt_v2_timestamp; + manifest.size = foo_egg_txt_v2_content.len() as _; + manifest.blocks = vec![foo_egg_txt_v2_block_access]; + }) + .map(|e| e.manifest.id); + + builder.store_stuff( + "wksp1_foo_egg_txt_v2_timestamp", + &wksp1_foo_egg_txt_v2_timestamp, + ); + + // On 2001-01-12, Bob uploads folder manifest `foo` (now named `foo2`) in v3 with rename `egg.txt`->`egg2.txt` and removed `spam` + + let wksp1_foo_v3_timestamp = "2001-01-12T00:00:00Z".parse::().unwrap(); + + builder + .create_or_update_folder_manifest_vlob("bob@dev1", wksp1_id, Some(wksp1_foo_id), None) + .customize(|e| { + let manifest = Arc::make_mut(&mut e.manifest); + manifest.timestamp = wksp1_foo_v3_timestamp; + manifest.children = + HashMap::from_iter([("egg2.txt".parse().unwrap(), wksp1_foo_egg_txt_id)]); + }); + + builder.store_stuff("wksp1_foo_v3_timestamp", &wksp1_foo_v3_timestamp); + + // On 2001-01-30, Bob gives back access to the workspace to Alice (as READER) + + let wksp1_alice_gets_back_access_timestamp = + "2001-01-30T00:00:00Z".parse::().unwrap(); + + builder + .share_realm(wksp1_id, "alice", Some(RealmRole::Reader)) + .customize(|e| { + e.timestamp = wksp1_alice_gets_back_access_timestamp; + }); + + builder.store_stuff( + "wksp1_alice_gets_back_access_timestamp", + &wksp1_alice_gets_back_access_timestamp, + ); + + // 3) Initialize client storages for alice@dev1, bob@dev1 and mallory@dev1 + + builder + .counters + .set_current_timestamp("2001-02-01T00:00:00Z".parse().unwrap()); + + macro_rules! fetch_and_update_local_storage { + ($device: literal) => { + builder.certificates_storage_fetch_certificates($device); + builder + .user_storage_local_update($device) + .update_local_workspaces_with_fetched_certificates(); + builder.user_storage_fetch_realm_checkpoint($device); + builder.workspace_data_storage_fetch_realm_checkpoint($device, wksp1_id); + }; + } + fetch_and_update_local_storage!("alice@dev1"); + fetch_and_update_local_storage!("bob@dev1"); + fetch_and_update_local_storage!("mallory@dev1"); + + builder.finalize() +} From 00ce05a5601cadf0edc60c4992d99c00609a103b Mon Sep 17 00:00:00 2001 From: Emmanuel Leblond Date: Fri, 18 Oct 2024 20:47:16 +0200 Subject: [PATCH 4/8] Implement workspace history in libparsec_client --- .../crates/client/src/workspace/fetch.rs | 138 +++++- .../client/src/workspace/history/fd_close.rs | 26 + .../client/src/workspace/history/fd_read.rs | 101 ++++ .../client/src/workspace/history/fd_stat.rs | 42 ++ .../get_workspace_manifest_v1_timestamp.rs | 113 +++++ .../client/src/workspace/history/mod.rs | 247 ++++++++++ .../client/src/workspace/history/open_file.rs | 125 +++++ .../src/workspace/history/populate_cache.rs | 142 ++++++ .../src/workspace/history/read_folder.rs | 454 ++++++++++++++++++ .../src/workspace/history/resolve_path.rs | 402 ++++++++++++++++ .../src/workspace/history/stat_entry.rs | 160 ++++++ libparsec/crates/client/src/workspace/mod.rs | 19 +- .../client/src/workspace/store/cache.rs | 1 + .../tests/unit/workspace/history/fd_close.rs | 82 ++++ .../tests/unit/workspace/history/fd_read.rs | 294 ++++++++++++ .../tests/unit/workspace/history/fd_stat.rs | 137 ++++++ .../get_workspace_manifest_v1_timestamp.rs | 208 ++++++++ .../tests/unit/workspace/history/mod.rs | 12 + .../tests/unit/workspace/history/open_file.rs | 209 ++++++++ .../unit/workspace/history/open_file_by_id.rs | 157 ++++++ .../unit/workspace/history/stat_entry.rs | 234 +++++++++ .../workspace/history/stat_entry_by_id.rs | 228 +++++++++ .../workspace/history/stat_folder_children.rs | 317 ++++++++++++ .../history/stat_folder_children_by_id.rs | 300 ++++++++++++ .../crates/client/tests/unit/workspace/mod.rs | 1 + 25 files changed, 4141 insertions(+), 8 deletions(-) create mode 100644 libparsec/crates/client/src/workspace/history/fd_close.rs create mode 100644 libparsec/crates/client/src/workspace/history/fd_read.rs create mode 100644 libparsec/crates/client/src/workspace/history/fd_stat.rs create mode 100644 libparsec/crates/client/src/workspace/history/get_workspace_manifest_v1_timestamp.rs create mode 100644 libparsec/crates/client/src/workspace/history/mod.rs create mode 100644 libparsec/crates/client/src/workspace/history/open_file.rs create mode 100644 libparsec/crates/client/src/workspace/history/populate_cache.rs create mode 100644 libparsec/crates/client/src/workspace/history/read_folder.rs create mode 100644 libparsec/crates/client/src/workspace/history/resolve_path.rs create mode 100644 libparsec/crates/client/src/workspace/history/stat_entry.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/fd_close.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/fd_read.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/fd_stat.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/get_workspace_manifest_v1_timestamp.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/mod.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/open_file.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/open_file_by_id.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/stat_entry.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/stat_entry_by_id.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/stat_folder_children.rs create mode 100644 libparsec/crates/client/tests/unit/workspace/history/stat_folder_children_by_id.rs diff --git a/libparsec/crates/client/src/workspace/fetch.rs b/libparsec/crates/client/src/workspace/fetch.rs index 1ae4b1b392d..c71962c9d3b 100644 --- a/libparsec/crates/client/src/workspace/fetch.rs +++ b/libparsec/crates/client/src/workspace/fetch.rs @@ -49,8 +49,9 @@ pub(super) async fn fetch_remote_child_manifest( certificates_ops: &CertificateOps, realm_id: VlobID, vlob_id: VlobID, + at: Option, ) -> Result { - let data = fetch_vlob(cmds, realm_id, vlob_id).await?; + let data = fetch_vlob(cmds, realm_id, vlob_id, at).await?; certificates_ops .validate_child_manifest( @@ -84,14 +85,14 @@ pub(super) async fn fetch_remote_child_manifest( }) } -#[allow(unused)] pub(super) async fn fetch_remote_workspace_manifest( cmds: &AuthenticatedCmds, certificates_ops: &CertificateOps, realm_id: VlobID, + at: Option, ) -> Result { let vlob_id = realm_id; // Remember: workspace manifest's ID *is* the realm ID ! - let data = fetch_vlob(cmds, realm_id, vlob_id).await?; + let data = fetch_vlob(cmds, realm_id, vlob_id, at).await?; certificates_ops .validate_workspace_manifest( @@ -138,13 +139,14 @@ async fn fetch_vlob( cmds: &AuthenticatedCmds, realm_id: VlobID, vlob_id: VlobID, + at: Option, ) -> Result { use authenticated_cmds::latest::vlob_read_batch::{Rep, Req}; let req = Req { realm_id, vlobs: vec![vlob_id], - at: None, + at, }; let rep = cmds.send(req).await?; @@ -272,3 +274,131 @@ pub(super) async fn fetch_block( Ok(data) } + +#[derive(Debug, thiserror::Error)] +pub enum FetchVersionsRemoteManifestError { + #[error("Component has stopped")] + Stopped, + #[error("Cannot reach the server")] + Offline, + #[error("The manifest's realm doesn't exist on the server")] + RealmNotFound, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + InvalidManifest(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +impl From for FetchVersionsRemoteManifestError { + fn from(value: ConnectionError) -> Self { + match value { + ConnectionError::NoResponse(_) => Self::Offline, + err => Self::Internal(err.into()), + } + } +} + +pub(super) async fn fetch_versions_remote_workspace_manifest( + cmds: &AuthenticatedCmds, + certificates_ops: &CertificateOps, + realm_id: VlobID, + versions: &[VersionInt], +) -> Result, FetchVersionsRemoteManifestError> { + let VlobVersionsData { + needed_common_certificate_timestamp, + needed_realm_certificate_timestamp, + items, + } = fetch_versions_vlob( + cmds, + realm_id, + versions.iter().map(|version| (realm_id, *version)), + ) + .await?; + + let mut manifests = Vec::with_capacity(items.len()); + for (_, key_index, expected_author, expected_version, expected_timestamp, blob) in items { + let manifest = certificates_ops + .validate_workspace_manifest( + needed_realm_certificate_timestamp, + needed_common_certificate_timestamp, + realm_id, + key_index, + expected_author, + expected_version, + expected_timestamp, + &blob, + ) + .await + .map_err(|err| match err { + CertifValidateManifestError::Offline => FetchVersionsRemoteManifestError::Offline, + CertifValidateManifestError::Stopped => FetchVersionsRemoteManifestError::Stopped, + CertifValidateManifestError::NotAllowed => { + FetchVersionsRemoteManifestError::NoRealmAccess + } + CertifValidateManifestError::InvalidManifest(err) => { + FetchVersionsRemoteManifestError::InvalidManifest(err) + } + CertifValidateManifestError::InvalidCertificate(err) => { + FetchVersionsRemoteManifestError::InvalidCertificate(err) + } + CertifValidateManifestError::InvalidKeysBundle(err) => { + FetchVersionsRemoteManifestError::InvalidKeysBundle(err) + } + CertifValidateManifestError::Internal(err) => { + err.context("Cannot validate vlob").into() + } + })?; + manifests.push(manifest); + } + + Ok(manifests) +} + +struct VlobVersionsData { + needed_common_certificate_timestamp: DateTime, + needed_realm_certificate_timestamp: DateTime, + items: Vec<(VlobID, IndexInt, DeviceID, VersionInt, DateTime, Bytes)>, +} + +async fn fetch_versions_vlob( + cmds: &AuthenticatedCmds, + realm_id: VlobID, + items: impl Iterator, +) -> Result { + use authenticated_cmds::latest::vlob_read_versions::{Rep, Req}; + + let req = Req { + realm_id, + items: items.collect(), + }; + + let rep = cmds.send(req).await?; + + match rep { + Rep::Ok { needed_common_certificate_timestamp, needed_realm_certificate_timestamp, items } => { + Ok(VlobVersionsData { + needed_common_certificate_timestamp, + needed_realm_certificate_timestamp, + items, + }) + }, + // Expected errors + Rep::AuthorNotAllowed => Err(FetchVersionsRemoteManifestError::NoRealmAccess), + Rep::RealmNotFound => Err(FetchVersionsRemoteManifestError::RealmNotFound), + // Unexpected errors :( + rep @ ( + // One item is too many ???? Really ???? + Rep::TooManyElements | + // Don't know what to do with this status :/ + Rep::UnknownStatus { .. } + ) => { + Err(anyhow::anyhow!("Unexpected server response: {:?}", rep).into()) + }, + } +} diff --git a/libparsec/crates/client/src/workspace/history/fd_close.rs b/libparsec/crates/client/src/workspace/history/fd_close.rs new file mode 100644 index 00000000000..57ee395d4a0 --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/fd_close.rs @@ -0,0 +1,26 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_types::prelude::*; + +use crate::workspace::WorkspaceHistoryOps; + +#[derive(Debug, thiserror::Error)] +pub enum WorkspaceHistoryFdCloseError { + #[error("File descriptor not found")] + BadFileDescriptor, + // Internal used in main `libparsec` crate to handle bad workspace handle. + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +pub(crate) fn fd_close( + ops: &WorkspaceHistoryOps, + fd: FileDescriptor, +) -> Result<(), WorkspaceHistoryFdCloseError> { + let mut cache = ops.cache.lock().expect("Mutex is poisoned"); + + match cache.opened_files.remove(&fd) { + Some(_) => Ok(()), + None => Err(WorkspaceHistoryFdCloseError::BadFileDescriptor), + } +} diff --git a/libparsec/crates/client/src/workspace/history/fd_read.rs b/libparsec/crates/client/src/workspace/history/fd_read.rs new file mode 100644 index 00000000000..fc554592337 --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/fd_read.rs @@ -0,0 +1,101 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::ConnectionError; +use libparsec_types::prelude::*; + +use super::{WorkspaceHistoryGetBlockError, WorkspaceHistoryOps}; +use crate::{InvalidBlockAccessError, InvalidCertificateError, InvalidKeysBundleError}; + +#[derive(Debug, thiserror::Error)] +pub enum WorkspaceHistoryFdReadError { + #[error("Cannot reach the server")] + Offline, + #[error("Component has stopped")] + Stopped, + #[error("File descriptor not found")] + BadFileDescriptor, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error("Block doesn't exist on the server")] + BlockNotFound, + #[error(transparent)] + InvalidBlockAccess(#[from] Box), + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +impl From for WorkspaceHistoryFdReadError { + fn from(value: ConnectionError) -> Self { + match value { + ConnectionError::NoResponse(_) => Self::Offline, + err => Self::Internal(err.into()), + } + } +} + +pub async fn fd_read( + ops: &WorkspaceHistoryOps, + fd: FileDescriptor, + offset: u64, + size: u64, + buf: &mut impl std::io::Write, +) -> Result { + let manifest = { + let cache = ops.cache.lock().expect("Mutex is poisoned"); + let manifest = cache + .opened_files + .get(&fd) + .ok_or(WorkspaceHistoryFdReadError::BadFileDescriptor)?; + manifest.clone() + }; + + let (written_size, chunk_views) = + crate::workspace::transactions::prepare_read(&manifest, size, offset); + let mut buf_size = 0; + for chunk_view in chunk_views { + let access = chunk_view + .access + .as_ref() + .expect("history only works on blocks"); + let chunk_data = ops + .get_block(access, &manifest.base) + .await + .map_err(|err| match err { + WorkspaceHistoryGetBlockError::Offline => WorkspaceHistoryFdReadError::Offline, + WorkspaceHistoryGetBlockError::Stopped + | WorkspaceHistoryGetBlockError::StoreUnavailable => { + WorkspaceHistoryFdReadError::Stopped + } + WorkspaceHistoryGetBlockError::NoRealmAccess => { + WorkspaceHistoryFdReadError::NoRealmAccess + } + WorkspaceHistoryGetBlockError::BlockNotFound => { + WorkspaceHistoryFdReadError::BlockNotFound + } + WorkspaceHistoryGetBlockError::InvalidBlockAccess(err) => { + WorkspaceHistoryFdReadError::InvalidBlockAccess(err) + } + WorkspaceHistoryGetBlockError::InvalidCertificate(err) => { + WorkspaceHistoryFdReadError::InvalidCertificate(err) + } + WorkspaceHistoryGetBlockError::InvalidKeysBundle(err) => { + WorkspaceHistoryFdReadError::InvalidKeysBundle(err) + } + WorkspaceHistoryGetBlockError::Internal(err) => { + err.context("cannot read chunk").into() + } + })?; + chunk_view + .copy_between_start_and_stop(&chunk_data, offset, buf, &mut buf_size) + .expect("prepare_read/buf/size are consistent"); + } + if buf_size < written_size as usize { + buf.write_all(&vec![0; written_size as usize - buf_size]) + .expect("write_all should not fail"); + } + Ok(written_size) +} diff --git a/libparsec/crates/client/src/workspace/history/fd_stat.rs b/libparsec/crates/client/src/workspace/history/fd_stat.rs new file mode 100644 index 00000000000..0554be288bc --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/fd_stat.rs @@ -0,0 +1,42 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_types::prelude::*; + +use crate::workspace::WorkspaceHistoryOps; + +#[derive(Debug, Clone)] +pub struct WorkspaceHistoryFileStat { + pub id: VlobID, + pub created: DateTime, + pub updated: DateTime, + pub version: VersionInt, + pub size: SizeInt, +} + +#[derive(Debug, thiserror::Error)] +pub enum WorkspaceHistoryFdStatError { + #[error("File descriptor not found")] + BadFileDescriptor, + // Internal used in main `libparsec` crate to handle bad workspace handle. + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +pub async fn fd_stat( + ops: &WorkspaceHistoryOps, + fd: FileDescriptor, +) -> Result { + let cache = ops.cache.lock().expect("Mutex is poisoned"); + let manifest = cache + .opened_files + .get(&fd) + .ok_or(WorkspaceHistoryFdStatError::BadFileDescriptor)?; + + Ok(WorkspaceHistoryFileStat { + id: manifest.base.id, + created: manifest.base.created, + updated: manifest.updated, + version: manifest.base.version, + size: manifest.size, + }) +} diff --git a/libparsec/crates/client/src/workspace/history/get_workspace_manifest_v1_timestamp.rs b/libparsec/crates/client/src/workspace/history/get_workspace_manifest_v1_timestamp.rs new file mode 100644 index 00000000000..07b33336317 --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/get_workspace_manifest_v1_timestamp.rs @@ -0,0 +1,113 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::ConnectionError; +use libparsec_types::prelude::*; + +use super::super::fetch::{ + fetch_versions_remote_workspace_manifest, FetchVersionsRemoteManifestError, +}; +use super::WorkspaceHistoryOps; +use crate::certif::{InvalidCertificateError, InvalidKeysBundleError, InvalidManifestError}; + +#[derive(Debug, thiserror::Error)] +pub enum WorkspaceHistoryGetWorkspaceManifestV1TimestampError { + #[error("Cannot reach the server")] + Offline, + #[error("Component has stopped")] + Stopped, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + InvalidManifest(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +impl From for WorkspaceHistoryGetWorkspaceManifestV1TimestampError { + fn from(value: ConnectionError) -> Self { + match value { + ConnectionError::NoResponse(_) => Self::Offline, + err => Self::Internal(err.into()), + } + } +} + +/// Earliest date we can go back. +/// +/// It's possible to have non root manifest older than this date, however +/// they were in practice not accessible until the workspace manifest got updated. +pub async fn get_workspace_manifest_v1_timestamp( + ops: &WorkspaceHistoryOps, +) -> Result, WorkspaceHistoryGetWorkspaceManifestV1TimestampError> { + // Cache lookup... + + { + let guard = ops.cache.lock().expect("Mutex is poisoned"); + if let Some(timestamp) = guard.workspace_manifest_v1_timestamp { + return Ok(Some(timestamp)); + } + } + + // Cache miss ! + + let outcome = fetch_versions_remote_workspace_manifest( + &ops.cmds, + &ops.certificates_ops, + ops.realm_id, + &[1], + ) + .await; + + let workspace_manifest_v1_timestamp = match outcome { + Ok(manifest) => match manifest.first() { + Some(manifest) => manifest.timestamp, + // Manifest has never been uploaded to the server yet + None => return Ok(None), + }, + Err(err) => { + return match err { + // The realm doesn't exist on server side, hence we are its creator and + // the manifest has never been uploaded to the server yet. + FetchVersionsRemoteManifestError::RealmNotFound => Ok(None), + + // Actual errors + FetchVersionsRemoteManifestError::Stopped => { + Err(WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Stopped) + } + FetchVersionsRemoteManifestError::Offline => { + Err(WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Offline) + } + FetchVersionsRemoteManifestError::NoRealmAccess => { + Err(WorkspaceHistoryGetWorkspaceManifestV1TimestampError::NoRealmAccess) + } + FetchVersionsRemoteManifestError::InvalidKeysBundle(err) => Err( + WorkspaceHistoryGetWorkspaceManifestV1TimestampError::InvalidKeysBundle(err), + ), + FetchVersionsRemoteManifestError::InvalidCertificate(err) => Err( + WorkspaceHistoryGetWorkspaceManifestV1TimestampError::InvalidCertificate(err), + ), + FetchVersionsRemoteManifestError::InvalidManifest(err) => { + Err(WorkspaceHistoryGetWorkspaceManifestV1TimestampError::InvalidManifest(err)) + } + FetchVersionsRemoteManifestError::Internal(err) => Err(err.into()), + }; + } + }; + + // Update cache and return + + let mut guard = ops.cache.lock().expect("Mutex is poisoned"); + match guard.workspace_manifest_v1_timestamp { + None => { + guard.workspace_manifest_v1_timestamp = Some(workspace_manifest_v1_timestamp); + Ok(guard.workspace_manifest_v1_timestamp) + } + // Concurrent operation has updated the cache, let's just pretend it was + // available when we first checked it. + Some(timestamp) => Ok(Some(timestamp)), + } +} diff --git a/libparsec/crates/client/src/workspace/history/mod.rs b/libparsec/crates/client/src/workspace/history/mod.rs new file mode 100644 index 00000000000..66c7a92032f --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/mod.rs @@ -0,0 +1,247 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +mod fd_close; +mod fd_read; +mod fd_stat; +mod get_workspace_manifest_v1_timestamp; +mod open_file; +mod populate_cache; +mod read_folder; +mod resolve_path; +mod stat_entry; + +mod transactions { + pub use super::fd_close::*; + pub use super::fd_read::*; + pub use super::fd_stat::*; + pub use super::get_workspace_manifest_v1_timestamp::*; + pub use super::open_file::*; + pub use super::read_folder::*; + pub(super) use super::resolve_path::*; + pub use super::stat_entry::*; +} + +pub use transactions::{ + WorkspaceHistoryEntryStat, WorkspaceHistoryFdCloseError, WorkspaceHistoryFdReadError, + WorkspaceHistoryFdStatError, WorkspaceHistoryFileStat, WorkspaceHistoryFolderReader, + WorkspaceHistoryFolderReaderStatEntryError, WorkspaceHistoryFolderReaderStatNextOutcome, + WorkspaceHistoryGetWorkspaceManifestV1TimestampError, WorkspaceHistoryOpenFileError, + WorkspaceHistoryOpenFolderReaderError, WorkspaceHistoryStatEntryError, + WorkspaceHistoryStatFolderChildrenError, +}; +use transactions::{ + WorkspaceHistoryGetBlockError, WorkspaceHistoryGetEntryError, WorkspaceHistoryResolvePathError, +}; + +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use libparsec_client_connection::AuthenticatedCmds; +use libparsec_types::prelude::*; + +use crate::{CertificateOps, ClientConfig}; + +#[derive(Debug)] +enum CacheResolvedEntry { + // TODO: we could be using remote manifests here (which should roughly divide + // the memory consumption by 2) since we only do read operations. + Exists(ArcLocalChildManifest), + NotFound, +} + +struct Cache { + resolutions: HashMap>, + next_file_descriptor: FileDescriptor, + // Given the files are only opened for read, each open only needs to have access + // the manifest at the given point in time. + // Note multiple opens of the same file leave to multiple entries in the map + // which is totally fine since no write operation is allowed. + opened_files: HashMap>, + blocks: HashMap, + /// Earliest date we can go back. + workspace_manifest_v1_timestamp: Option, +} + +pub struct WorkspaceHistoryOps { + #[allow(unused)] + config: Arc, + cmds: Arc, + certificates_ops: Arc, + cache: Mutex, + realm_id: VlobID, +} + +impl WorkspaceHistoryOps { + pub(crate) fn new( + config: Arc, + cmds: Arc, + certificates_ops: Arc, + realm_id: VlobID, + ) -> Self { + Self { + config, + cmds, + certificates_ops, + realm_id, + cache: Mutex::new(Cache { + resolutions: HashMap::new(), + // Avoid using 0 as file descriptor, as it is error-prone + next_file_descriptor: FileDescriptor(1), + opened_files: HashMap::new(), + blocks: HashMap::new(), + workspace_manifest_v1_timestamp: None, + }), + } + } + + /* + * Internal helpers + */ + + async fn resolve_path( + &self, + at: DateTime, + path: &FsPath, + ) -> Result { + transactions::resolve_path(self, at, path).await + } + + #[allow(unused)] + async fn retrieve_path_from_id( + ops: &WorkspaceHistoryOps, + at: DateTime, + entry_id: VlobID, + ) -> Result<(ArcLocalChildManifest, FsPath), WorkspaceHistoryResolvePathError> { + transactions::retrieve_path_from_id(ops, at, entry_id).await + } + + async fn get_entry( + &self, + at: DateTime, + entry_id: VlobID, + ) -> Result { + transactions::get_entry(self, at, entry_id).await + } + + async fn get_block( + &self, + access: &BlockAccess, + remote_manifest: &FileManifest, + ) -> Result { + transactions::get_block(self, access, remote_manifest).await + } + + /* + * Public API + */ + + /// Earliest date we can go back. + /// + /// It's possible to have non root manifest older than this date, however + /// they were in practice not accessible until the root manifest got updated. + pub async fn get_workspace_manifest_v1_timestamp( + &self, + ) -> Result, WorkspaceHistoryGetWorkspaceManifestV1TimestampError> { + transactions::get_workspace_manifest_v1_timestamp(self).await + } + + pub async fn stat_entry( + &self, + at: DateTime, + path: &FsPath, + ) -> Result { + transactions::stat_entry(self, at, path).await + } + + pub async fn stat_entry_by_id( + &self, + at: DateTime, + entry_id: VlobID, + ) -> Result { + transactions::stat_entry_by_id(self, at, entry_id).await + } + + pub async fn open_folder_reader( + &self, + at: DateTime, + path: &FsPath, + ) -> Result { + transactions::open_folder_reader(self, at, path).await + } + + pub async fn open_folder_reader_by_id( + &self, + at: DateTime, + entry_id: VlobID, + ) -> Result { + transactions::open_folder_reader_by_id(self, at, entry_id).await + } + + /// Note children are listed in arbitrary order, and there is no '.' and '..' special entries. + pub async fn stat_folder_children( + &self, + at: DateTime, + path: &FsPath, + ) -> Result, WorkspaceHistoryStatFolderChildrenError> + { + transactions::stat_folder_children(self, at, path).await + } + + pub async fn stat_folder_children_by_id( + &self, + at: DateTime, + entry_id: VlobID, + ) -> Result, WorkspaceHistoryStatFolderChildrenError> + { + transactions::stat_folder_children_by_id(self, at, entry_id).await + } + + pub async fn open_file( + &self, + at: DateTime, + path: FsPath, + ) -> Result { + transactions::open_file(self, at, path) + .await + .map(|(fd, _)| fd) + } + + pub async fn open_file_by_id( + &self, + at: DateTime, + entry_id: VlobID, + ) -> Result { + transactions::open_file_by_id(self, at, entry_id).await + } + + pub async fn open_file_and_get_id( + &self, + at: DateTime, + path: FsPath, + ) -> Result<(FileDescriptor, VlobID), WorkspaceHistoryOpenFileError> { + transactions::open_file(self, at, path).await + } + + pub fn fd_close(&self, fd: FileDescriptor) -> Result<(), WorkspaceHistoryFdCloseError> { + transactions::fd_close(self, fd) + } + + pub async fn fd_read( + &self, + fd: FileDescriptor, + offset: u64, + size: u64, + buf: &mut impl std::io::Write, + ) -> Result { + transactions::fd_read(self, fd, offset, size, buf).await + } + + pub async fn fd_stat( + &self, + fd: FileDescriptor, + ) -> Result { + transactions::fd_stat(self, fd).await + } +} diff --git a/libparsec/crates/client/src/workspace/history/open_file.rs b/libparsec/crates/client/src/workspace/history/open_file.rs new file mode 100644 index 00000000000..f92a469bd4c --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/open_file.rs @@ -0,0 +1,125 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::ConnectionError; +use libparsec_types::prelude::*; + +use super::{WorkspaceHistoryGetEntryError, WorkspaceHistoryOps, WorkspaceHistoryResolvePathError}; +use crate::certif::{InvalidCertificateError, InvalidKeysBundleError, InvalidManifestError}; + +#[derive(Debug, thiserror::Error)] +pub enum WorkspaceHistoryOpenFileError { + #[error("Cannot reach the server")] + Offline, + #[error("Component has stopped")] + Stopped, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error("Path doesn't exist")] + EntryNotFound, + #[error("Path points to an entry (ID: `{}`) that is not a file", .entry_id)] + EntryNotAFile { entry_id: VlobID }, + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + InvalidManifest(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +impl From for WorkspaceHistoryOpenFileError { + fn from(value: ConnectionError) -> Self { + match value { + ConnectionError::NoResponse(_) => Self::Offline, + err => Self::Internal(err.into()), + } + } +} + +pub async fn open_file( + ops: &WorkspaceHistoryOps, + at: DateTime, + path: FsPath, +) -> Result<(FileDescriptor, VlobID), WorkspaceHistoryOpenFileError> { + let manifest = ops.resolve_path(at, &path).await.map_err(|err| match err { + WorkspaceHistoryResolvePathError::Offline => WorkspaceHistoryOpenFileError::Offline, + WorkspaceHistoryResolvePathError::Stopped => WorkspaceHistoryOpenFileError::Stopped, + WorkspaceHistoryResolvePathError::EntryNotFound => { + WorkspaceHistoryOpenFileError::EntryNotFound + } + WorkspaceHistoryResolvePathError::NoRealmAccess => { + WorkspaceHistoryOpenFileError::NoRealmAccess + } + WorkspaceHistoryResolvePathError::InvalidKeysBundle(invalid_keys_bundle_error) => { + WorkspaceHistoryOpenFileError::InvalidKeysBundle(invalid_keys_bundle_error) + } + WorkspaceHistoryResolvePathError::InvalidCertificate(invalid_certificate_error) => { + WorkspaceHistoryOpenFileError::InvalidCertificate(invalid_certificate_error) + } + WorkspaceHistoryResolvePathError::InvalidManifest(invalid_manifest_error) => { + WorkspaceHistoryOpenFileError::InvalidManifest(invalid_manifest_error) + } + WorkspaceHistoryResolvePathError::Internal(err) => err.into(), + })?; + + let manifest = match manifest { + ArcLocalChildManifest::File(manifest) => manifest, + ArcLocalChildManifest::Folder(manifest) => { + return Err(WorkspaceHistoryOpenFileError::EntryNotAFile { + entry_id: manifest.base.id, + }); + } + }; + let entry_id = manifest.base.id; + + let mut cache = ops.cache.lock().expect("Mutex is poisoned"); + let fd = cache.next_file_descriptor; + cache.next_file_descriptor.0 += 1; + cache.opened_files.insert(fd, manifest); + + Ok((fd, entry_id)) +} + +pub async fn open_file_by_id( + ops: &WorkspaceHistoryOps, + at: DateTime, + entry_id: VlobID, +) -> Result { + let manifest = ops.get_entry(at, entry_id).await.map_err(|err| match err { + WorkspaceHistoryGetEntryError::Offline => WorkspaceHistoryOpenFileError::Offline, + WorkspaceHistoryGetEntryError::Stopped => WorkspaceHistoryOpenFileError::Stopped, + WorkspaceHistoryGetEntryError::EntryNotFound => { + WorkspaceHistoryOpenFileError::EntryNotFound + } + WorkspaceHistoryGetEntryError::NoRealmAccess => { + WorkspaceHistoryOpenFileError::NoRealmAccess + } + WorkspaceHistoryGetEntryError::InvalidKeysBundle(invalid_keys_bundle_error) => { + WorkspaceHistoryOpenFileError::InvalidKeysBundle(invalid_keys_bundle_error) + } + WorkspaceHistoryGetEntryError::InvalidCertificate(invalid_certificate_error) => { + WorkspaceHistoryOpenFileError::InvalidCertificate(invalid_certificate_error) + } + WorkspaceHistoryGetEntryError::InvalidManifest(invalid_manifest_error) => { + WorkspaceHistoryOpenFileError::InvalidManifest(invalid_manifest_error) + } + WorkspaceHistoryGetEntryError::Internal(err) => err.into(), + })?; + + let manifest = match manifest { + ArcLocalChildManifest::File(manifest) => manifest, + ArcLocalChildManifest::Folder(manifest) => { + return Err(WorkspaceHistoryOpenFileError::EntryNotAFile { + entry_id: manifest.base.id, + }); + } + }; + + let mut cache = ops.cache.lock().expect("Mutex is poisoned"); + let fd = cache.next_file_descriptor; + cache.next_file_descriptor.0 += 1; + cache.opened_files.insert(fd, manifest); + + Ok(fd) +} diff --git a/libparsec/crates/client/src/workspace/history/populate_cache.rs b/libparsec/crates/client/src/workspace/history/populate_cache.rs new file mode 100644 index 00000000000..92b87e51796 --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/populate_cache.rs @@ -0,0 +1,142 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use std::{collections::HashMap, sync::Arc}; + +use libparsec_types::prelude::*; + +use crate::{ + certif::{InvalidCertificateError, InvalidKeysBundleError, InvalidManifestError}, + workspace::fetch::FetchRemoteManifestError, +}; + +use super::CacheResolvedEntry; + +#[derive(Debug, thiserror::Error)] +pub(super) enum PopulateCacheFromServerError { + #[error("Cannot reach the server")] + Offline, + #[error("Component has stopped")] + Stopped, + #[error("Path doesn't exist")] + EntryNotFound, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + InvalidManifest(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +pub(super) async fn populate_cache_from_server( + ops: &super::WorkspaceHistoryOps, + at: DateTime, + entry_id: VlobID, +) -> Result { + // 1) Server lookup + + let outcome = if ops.realm_id == entry_id { + super::super::fetch::fetch_remote_workspace_manifest( + &ops.cmds, + &ops.certificates_ops, + ops.realm_id, + Some(at), + ) + .await + .map(ChildManifest::Folder) + } else { + super::super::fetch::fetch_remote_child_manifest( + &ops.cmds, + &ops.certificates_ops, + ops.realm_id, + entry_id, + Some(at), + ) + .await + }; + + let maybe_manifest = match outcome { + Ok(ChildManifest::File(manifest)) => Some(ArcLocalChildManifest::File(Arc::new( + LocalFileManifest::from_remote(manifest), + ))), + Ok(ChildManifest::Folder(manifest)) => Some(ArcLocalChildManifest::Folder(Arc::new( + LocalFolderManifest::from_remote(manifest, &Regex::empty()), + ))), + // This is unexpected: we got an entry ID from a parent folder/workspace + // manifest, but this ID points to nothing according to the server :/ + // + // That could means two things: + // - the server is lying to us + // - the client that have uploaded the parent folder/workspace manifest + // was buggy and include the ID of a not-yet-synchronized entry + // + // We just pretend the entry doesn't exist + Err(FetchRemoteManifestError::VlobNotFound) => None, + Err(FetchRemoteManifestError::Stopped) => { + return Err(PopulateCacheFromServerError::Stopped); + } + Err(FetchRemoteManifestError::Offline) => { + return Err(PopulateCacheFromServerError::Offline); + } + // The realm doesn't exist on server side, hence we are it creator and + // it data only live on our local storage, which we have already checked. + Err(FetchRemoteManifestError::RealmNotFound) => { + return Err(PopulateCacheFromServerError::EntryNotFound); + } + Err(FetchRemoteManifestError::NoRealmAccess) => { + return Err(PopulateCacheFromServerError::NoRealmAccess); + } + Err(FetchRemoteManifestError::InvalidKeysBundle(err)) => { + return Err(PopulateCacheFromServerError::InvalidKeysBundle(err)); + } + Err(FetchRemoteManifestError::InvalidCertificate(err)) => { + return Err(PopulateCacheFromServerError::InvalidCertificate(err)); + } + Err(FetchRemoteManifestError::InvalidManifest(err)) => { + return Err(PopulateCacheFromServerError::InvalidManifest(err)); + } + Err(FetchRemoteManifestError::Internal(err)) => { + return Err(err.context("cannot fetch from server").into()); + } + }; + + // 2) We got our manifest, update cache with it + + let mut cache = ops.cache.lock().expect("Mutex is poisoned"); + + let resolutions = match cache.resolutions.entry(at) { + std::collections::hash_map::Entry::Occupied(entry) => entry.into_mut(), + std::collections::hash_map::Entry::Vacant(entry) => entry.insert(HashMap::default()), + }; + + let manifest = match resolutions.entry(entry_id) { + std::collections::hash_map::Entry::Vacant(entry) => match maybe_manifest { + Some(manifest) => { + entry.insert(CacheResolvedEntry::Exists(manifest.clone())); + manifest + } + None => { + entry.insert(CacheResolvedEntry::NotFound); + return Err(PopulateCacheFromServerError::EntryNotFound); + } + }, + std::collections::hash_map::Entry::Occupied(entry) => { + // Plot twist: a concurrent operation has updated the cache ! + // So we discard the data we've fetched and pretend we got a cache hit in + // the first place. + match entry.get() { + CacheResolvedEntry::Exists(arc_local_child_manifest) => { + arc_local_child_manifest.to_owned() + } + CacheResolvedEntry::NotFound => { + return Err(PopulateCacheFromServerError::EntryNotFound) + } + } + } + }; + + Ok(manifest) +} diff --git a/libparsec/crates/client/src/workspace/history/read_folder.rs b/libparsec/crates/client/src/workspace/history/read_folder.rs new file mode 100644 index 00000000000..f6be6a2f450 --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/read_folder.rs @@ -0,0 +1,454 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use std::sync::Arc; + +use libparsec_client_connection::ConnectionError; +use libparsec_types::prelude::*; + +use super::{ + WorkspaceHistoryEntryStat, WorkspaceHistoryGetEntryError, WorkspaceHistoryOps, + WorkspaceHistoryResolvePathError, WorkspaceHistoryStatEntryError, +}; +use crate::certif::{InvalidCertificateError, InvalidKeysBundleError, InvalidManifestError}; + +#[derive(Debug, thiserror::Error)] +pub enum WorkspaceHistoryOpenFolderReaderError { + #[error("Cannot reach the server")] + Offline, + #[error("Component has stopped")] + Stopped, + #[error("Path doesn't exist")] + EntryNotFound, + #[error("Path points to a file")] + EntryIsFile, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + InvalidManifest(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +impl From for WorkspaceHistoryOpenFolderReaderError { + fn from(value: ConnectionError) -> Self { + match value { + ConnectionError::NoResponse(_) => Self::Offline, + err => Self::Internal(err.into()), + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum WorkspaceHistoryFolderReaderStatEntryError { + #[error("Cannot reach the server")] + Offline, + #[error("Component has stopped")] + Stopped, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + InvalidManifest(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +impl From for WorkspaceHistoryFolderReaderStatEntryError { + fn from(value: ConnectionError) -> Self { + match value { + ConnectionError::NoResponse(_) => Self::Offline, + err => Self::Internal(err.into()), + } + } +} + +#[derive(Debug)] +pub struct WorkspaceHistoryFolderReader { + at: DateTime, + manifest: Arc, +} + +#[derive(Debug)] +pub enum WorkspaceHistoryFolderReaderStatNextOutcome<'a> { + Entry { + name: &'a EntryName, + stat: WorkspaceHistoryEntryStat, + }, + /// The entry listen in the parent manifest turned not to be an actual + /// child (e.g. has been reparented) + InvalidChild, + NoMoreEntries, +} + +impl WorkspaceHistoryFolderReader { + // TODO: A possible future improvement here would be to read by batch in order + // to first ensure all children are available locally (and do a single + // batch request to the server if not) + + /// Return the stat of the folder itself. + pub fn stat_folder(&self) -> WorkspaceHistoryEntryStat { + WorkspaceHistoryEntryStat::Folder { + id: self.manifest.base.id, + parent: self.manifest.base.parent, + created: self.manifest.base.created, + updated: self.manifest.updated, + version: self.manifest.base.version, + } + } + + /// Note children are listed in arbitrary order, and there is no '.' and '..' special entries. + pub async fn stat_child<'a>( + &'a self, + ops: &WorkspaceHistoryOps, + index: usize, + ) -> Result< + WorkspaceHistoryFolderReaderStatNextOutcome, + WorkspaceHistoryFolderReaderStatEntryError, + > { + let expected_parent_id = self.manifest.base.id; + + let (child_name, child_id) = match self.manifest.children.iter().nth(index) { + Some((child_name, child_id)) => (child_name, *child_id), + None => return Ok(WorkspaceHistoryFolderReaderStatNextOutcome::NoMoreEntries), + }; + let child_stat = match ops.stat_entry_by_id(self.at, child_id).await { + Ok(stat) => stat, + Err(err) => { + return Err(match err { + // Special case: if the entry is not found it means this child is + // invalid (e.g. the entry has been reparented during a move) and + // should just be ignored. + WorkspaceHistoryStatEntryError::EntryNotFound => { + return Ok(WorkspaceHistoryFolderReaderStatNextOutcome::InvalidChild) + } + WorkspaceHistoryStatEntryError::Offline => { + WorkspaceHistoryFolderReaderStatEntryError::Offline + } + WorkspaceHistoryStatEntryError::Stopped => { + WorkspaceHistoryFolderReaderStatEntryError::Stopped + } + WorkspaceHistoryStatEntryError::NoRealmAccess => { + WorkspaceHistoryFolderReaderStatEntryError::NoRealmAccess + } + WorkspaceHistoryStatEntryError::InvalidKeysBundle(err) => { + WorkspaceHistoryFolderReaderStatEntryError::InvalidKeysBundle(err) + } + WorkspaceHistoryStatEntryError::InvalidCertificate(err) => { + WorkspaceHistoryFolderReaderStatEntryError::InvalidCertificate(err) + } + WorkspaceHistoryStatEntryError::InvalidManifest(err) => { + WorkspaceHistoryFolderReaderStatEntryError::InvalidManifest(err) + } + WorkspaceHistoryStatEntryError::Internal(err) => err.into(), + }); + } + }; + + // Last check is to ensure the parent and child manifests agree they are related, + // if that's not the case we just ignore this child and move to the next one. + let child_parent = match child_stat { + WorkspaceHistoryEntryStat::File { parent, .. } => parent, + WorkspaceHistoryEntryStat::Folder { parent, .. } => parent, + }; + if child_parent != expected_parent_id { + return Ok(WorkspaceHistoryFolderReaderStatNextOutcome::InvalidChild); + } + + Ok(WorkspaceHistoryFolderReaderStatNextOutcome::Entry { + name: child_name, + stat: child_stat, + }) + } + + /// Needed by WinFSP + pub async fn get_index_for_name( + &self, + ops: &WorkspaceHistoryOps, + name: &EntryName, + ) -> Result, WorkspaceHistoryFolderReaderStatEntryError> { + for (offset, (child_name, child_id)) in self.manifest.children.iter().enumerate() { + if child_name == name { + let child_manifest = match ops.get_entry(self.at, *child_id).await { + Ok(child_manifest) => child_manifest, + Err(err) => { + return Err(match err { + WorkspaceHistoryGetEntryError::EntryNotFound => continue, + WorkspaceHistoryGetEntryError::Offline => { + WorkspaceHistoryFolderReaderStatEntryError::Offline + } + WorkspaceHistoryGetEntryError::Stopped => { + WorkspaceHistoryFolderReaderStatEntryError::Stopped + } + WorkspaceHistoryGetEntryError::NoRealmAccess => { + WorkspaceHistoryFolderReaderStatEntryError::NoRealmAccess + } + WorkspaceHistoryGetEntryError::InvalidKeysBundle( + invalid_keys_bundle_error, + ) => WorkspaceHistoryFolderReaderStatEntryError::InvalidKeysBundle( + invalid_keys_bundle_error, + ), + WorkspaceHistoryGetEntryError::InvalidCertificate( + invalid_certificate_error, + ) => WorkspaceHistoryFolderReaderStatEntryError::InvalidCertificate( + invalid_certificate_error, + ), + WorkspaceHistoryGetEntryError::InvalidManifest( + invalid_manifest_error, + ) => WorkspaceHistoryFolderReaderStatEntryError::InvalidManifest( + invalid_manifest_error, + ), + WorkspaceHistoryGetEntryError::Internal(err) => err.into(), + }) + } + }; + + let actual_child = child_manifest.parent() == self.manifest.base.id; + if actual_child { + return Ok(Some(offset)); + } + } + } + Ok(None) + } +} + +pub async fn open_folder_reader_by_id( + ops: &WorkspaceHistoryOps, + at: DateTime, + entry_id: VlobID, +) -> Result { + let manifest = ops.get_entry(at, entry_id).await.map_err(|err| match err { + WorkspaceHistoryGetEntryError::Offline => WorkspaceHistoryOpenFolderReaderError::Offline, + WorkspaceHistoryGetEntryError::Stopped => WorkspaceHistoryOpenFolderReaderError::Stopped, + WorkspaceHistoryGetEntryError::EntryNotFound => { + WorkspaceHistoryOpenFolderReaderError::EntryNotFound + } + WorkspaceHistoryGetEntryError::NoRealmAccess => { + WorkspaceHistoryOpenFolderReaderError::NoRealmAccess + } + WorkspaceHistoryGetEntryError::InvalidKeysBundle(err) => { + WorkspaceHistoryOpenFolderReaderError::InvalidKeysBundle(err) + } + WorkspaceHistoryGetEntryError::InvalidCertificate(err) => { + WorkspaceHistoryOpenFolderReaderError::InvalidCertificate(err) + } + WorkspaceHistoryGetEntryError::InvalidManifest(err) => { + WorkspaceHistoryOpenFolderReaderError::InvalidManifest(err) + } + WorkspaceHistoryGetEntryError::Internal(err) => err.context("cannot retrieve path").into(), + })?; + + match manifest { + ArcLocalChildManifest::Folder(manifest) => { + Ok(WorkspaceHistoryFolderReader { at, manifest }) + } + ArcLocalChildManifest::File(_) => Err(WorkspaceHistoryOpenFolderReaderError::EntryIsFile), + } +} + +pub async fn open_folder_reader( + ops: &WorkspaceHistoryOps, + at: DateTime, + path: &FsPath, +) -> Result { + let manifest = ops.resolve_path(at, path).await.map_err(|err| match err { + WorkspaceHistoryResolvePathError::Offline => WorkspaceHistoryOpenFolderReaderError::Offline, + WorkspaceHistoryResolvePathError::Stopped => WorkspaceHistoryOpenFolderReaderError::Stopped, + WorkspaceHistoryResolvePathError::EntryNotFound => { + WorkspaceHistoryOpenFolderReaderError::EntryNotFound + } + WorkspaceHistoryResolvePathError::NoRealmAccess => { + WorkspaceHistoryOpenFolderReaderError::NoRealmAccess + } + WorkspaceHistoryResolvePathError::InvalidKeysBundle(err) => { + WorkspaceHistoryOpenFolderReaderError::InvalidKeysBundle(err) + } + WorkspaceHistoryResolvePathError::InvalidCertificate(err) => { + WorkspaceHistoryOpenFolderReaderError::InvalidCertificate(err) + } + WorkspaceHistoryResolvePathError::InvalidManifest(err) => { + WorkspaceHistoryOpenFolderReaderError::InvalidManifest(err) + } + WorkspaceHistoryResolvePathError::Internal(err) => { + err.context("cannot resolve path").into() + } + })?; + + match manifest { + ArcLocalChildManifest::Folder(manifest) => { + Ok(WorkspaceHistoryFolderReader { at, manifest }) + } + ArcLocalChildManifest::File(_) => Err(WorkspaceHistoryOpenFolderReaderError::EntryIsFile), + } +} + +#[derive(Debug, thiserror::Error)] +pub enum WorkspaceHistoryStatFolderChildrenError { + #[error("Cannot reach the server")] + Offline, + #[error("Component has stopped")] + Stopped, + #[error("Path doesn't exist")] + EntryNotFound, + #[error("Path points to a file")] + EntryIsFile, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + InvalidManifest(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +impl From for WorkspaceHistoryStatFolderChildrenError { + fn from(value: ConnectionError) -> Self { + match value { + ConnectionError::NoResponse(_) => Self::Offline, + err => Self::Internal(err.into()), + } + } +} + +pub async fn stat_folder_children( + ops: &WorkspaceHistoryOps, + at: DateTime, + path: &FsPath, +) -> Result, WorkspaceHistoryStatFolderChildrenError> { + let reader = open_folder_reader(ops, at, path) + .await + .map_err(|err| match err { + WorkspaceHistoryOpenFolderReaderError::Offline => { + WorkspaceHistoryStatFolderChildrenError::Offline + } + WorkspaceHistoryOpenFolderReaderError::Stopped => { + WorkspaceHistoryStatFolderChildrenError::Stopped + } + WorkspaceHistoryOpenFolderReaderError::EntryNotFound => { + WorkspaceHistoryStatFolderChildrenError::EntryNotFound + } + WorkspaceHistoryOpenFolderReaderError::EntryIsFile => { + WorkspaceHistoryStatFolderChildrenError::EntryIsFile + } + WorkspaceHistoryOpenFolderReaderError::NoRealmAccess => { + WorkspaceHistoryStatFolderChildrenError::NoRealmAccess + } + WorkspaceHistoryOpenFolderReaderError::InvalidKeysBundle(err) => { + WorkspaceHistoryStatFolderChildrenError::InvalidKeysBundle(err) + } + WorkspaceHistoryOpenFolderReaderError::InvalidCertificate(err) => { + WorkspaceHistoryStatFolderChildrenError::InvalidCertificate(err) + } + WorkspaceHistoryOpenFolderReaderError::InvalidManifest(err) => { + WorkspaceHistoryStatFolderChildrenError::InvalidManifest(err) + } + WorkspaceHistoryOpenFolderReaderError::Internal(err) => { + err.context("cannot open folder reader").into() + } + })?; + + consume_reader(ops, reader).await +} + +pub async fn stat_folder_children_by_id( + ops: &WorkspaceHistoryOps, + at: DateTime, + entry_id: VlobID, +) -> Result, WorkspaceHistoryStatFolderChildrenError> { + let reader = open_folder_reader_by_id(ops, at, entry_id) + .await + .map_err(|err| match err { + WorkspaceHistoryOpenFolderReaderError::Offline => { + WorkspaceHistoryStatFolderChildrenError::Offline + } + WorkspaceHistoryOpenFolderReaderError::Stopped => { + WorkspaceHistoryStatFolderChildrenError::Stopped + } + WorkspaceHistoryOpenFolderReaderError::EntryNotFound => { + WorkspaceHistoryStatFolderChildrenError::EntryNotFound + } + WorkspaceHistoryOpenFolderReaderError::EntryIsFile => { + WorkspaceHistoryStatFolderChildrenError::EntryIsFile + } + WorkspaceHistoryOpenFolderReaderError::NoRealmAccess => { + WorkspaceHistoryStatFolderChildrenError::NoRealmAccess + } + WorkspaceHistoryOpenFolderReaderError::InvalidKeysBundle(err) => { + WorkspaceHistoryStatFolderChildrenError::InvalidKeysBundle(err) + } + WorkspaceHistoryOpenFolderReaderError::InvalidCertificate(err) => { + WorkspaceHistoryStatFolderChildrenError::InvalidCertificate(err) + } + WorkspaceHistoryOpenFolderReaderError::InvalidManifest(err) => { + WorkspaceHistoryStatFolderChildrenError::InvalidManifest(err) + } + WorkspaceHistoryOpenFolderReaderError::Internal(err) => { + err.context("cannot open folder reader").into() + } + })?; + + consume_reader(ops, reader).await +} + +async fn consume_reader( + ops: &WorkspaceHistoryOps, + reader: WorkspaceHistoryFolderReader, +) -> Result, WorkspaceHistoryStatFolderChildrenError> { + // Manifest's children list may contains invalid entries (e.g. an entry that doesn't + // exist, or that has a different parent that us), so it's only a hint. + let max_children = reader.manifest.children.len(); + let mut children_stats = Vec::with_capacity(max_children); + + for index in 0..max_children { + let stat_outcome = reader + .stat_child(ops, index) + .await + .map_err(|err| match err { + WorkspaceHistoryFolderReaderStatEntryError::Offline => { + WorkspaceHistoryStatFolderChildrenError::Offline + } + WorkspaceHistoryFolderReaderStatEntryError::Stopped => { + WorkspaceHistoryStatFolderChildrenError::Stopped + } + WorkspaceHistoryFolderReaderStatEntryError::NoRealmAccess => { + WorkspaceHistoryStatFolderChildrenError::NoRealmAccess + } + WorkspaceHistoryFolderReaderStatEntryError::InvalidKeysBundle(err) => { + WorkspaceHistoryStatFolderChildrenError::InvalidKeysBundle(err) + } + WorkspaceHistoryFolderReaderStatEntryError::InvalidCertificate(err) => { + WorkspaceHistoryStatFolderChildrenError::InvalidCertificate(err) + } + WorkspaceHistoryFolderReaderStatEntryError::InvalidManifest(err) => { + WorkspaceHistoryStatFolderChildrenError::InvalidManifest(err) + } + WorkspaceHistoryFolderReaderStatEntryError::Internal(err) => { + err.context("cannot stat next child").into() + } + })?; + + match stat_outcome { + WorkspaceHistoryFolderReaderStatNextOutcome::Entry { + name: child_name, + stat: child_stat, + } => { + children_stats.push((child_name.to_owned(), child_stat)); + } + WorkspaceHistoryFolderReaderStatNextOutcome::InvalidChild => (), + // Our for loop already stops before `index` reaches `max_children` + WorkspaceHistoryFolderReaderStatNextOutcome::NoMoreEntries => unreachable!(), + } + } + + Ok(children_stats) +} diff --git a/libparsec/crates/client/src/workspace/history/resolve_path.rs b/libparsec/crates/client/src/workspace/history/resolve_path.rs new file mode 100644 index 00000000000..5183c3bbec3 --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/resolve_path.rs @@ -0,0 +1,402 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use std::collections::HashSet; + +use libparsec_types::prelude::*; + +use super::{ + populate_cache::{populate_cache_from_server, PopulateCacheFromServerError}, + WorkspaceHistoryOps, +}; +use crate::{ + certif::{InvalidCertificateError, InvalidKeysBundleError, InvalidManifestError}, + workspace::{fetch::FetchRemoteBlockError, history::CacheResolvedEntry}, +}; + +#[derive(Debug, thiserror::Error)] +pub(super) enum WorkspaceHistoryResolvePathError { + #[error("Cannot reach the server")] + Offline, + #[error("Component has stopped")] + Stopped, + #[error("Path doesn't exist")] + EntryNotFound, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + InvalidManifest(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +pub(super) async fn resolve_path( + ops: &WorkspaceHistoryOps, + at: DateTime, + path: &FsPath, +) -> Result { + // A word about circular path detection: + // - The path is resolved from the root, which itself is guaranteed to have its + // `parent` field pointing on itself. + // - Each time we resolve a child, we ensure the parent and child agree on the + // parenting relationship. + // + // This in practice means our valid data form a direct acyclic graph (DAG) and hence + // we cannot end up in a circular path. + // + // Note this is only valid because we start the resolution from the root, if we + // would be to start from elsewhere (typically to determine the path of a manifest + // by following its parent backward), we could end up in a circular path with + // manifests involved forming a "island" disconnect from the root. + + let path_parts = path.parts(); + + loop { + // Most of the time we should have each entry in the path already in the cache, + // so we want to lock the cache once and only release it in the unlikely case + // we need to fetch from the local storage or server. + let cache_only_outcome = { + let mut cache = ops.cache.lock().expect("Mutex is poisoned"); + cache_only_path_resolution(ops, &mut cache, at, path_parts) + }; + match cache_only_outcome { + CacheOnlyPathResolutionOutcome::Done(manifest) => return Ok(manifest), + CacheOnlyPathResolutionOutcome::EntryNotFound => { + return Err(WorkspaceHistoryResolvePathError::EntryNotFound) + } + // We got a cache miss + CacheOnlyPathResolutionOutcome::NeedPopulateCache(cache_miss_entry_id) => { + populate_cache_from_server(ops, at, cache_miss_entry_id) + .await + .map_err(|err| match err { + PopulateCacheFromServerError::Offline => { + WorkspaceHistoryResolvePathError::Offline + } + PopulateCacheFromServerError::Stopped => { + WorkspaceHistoryResolvePathError::Stopped + } + PopulateCacheFromServerError::EntryNotFound => { + WorkspaceHistoryResolvePathError::EntryNotFound + } + PopulateCacheFromServerError::NoRealmAccess => { + WorkspaceHistoryResolvePathError::NoRealmAccess + } + PopulateCacheFromServerError::InvalidKeysBundle(err) => { + WorkspaceHistoryResolvePathError::InvalidKeysBundle(err) + } + PopulateCacheFromServerError::InvalidCertificate(err) => { + WorkspaceHistoryResolvePathError::InvalidCertificate(err) + } + PopulateCacheFromServerError::InvalidManifest(err) => { + WorkspaceHistoryResolvePathError::InvalidManifest(err) + } + PopulateCacheFromServerError::Internal(err) => { + err.context("cannot fetch manifest").into() + } + })?; + } + } + } +} + +enum CacheOnlyPathResolutionOutcome { + Done(ArcLocalChildManifest), + EntryNotFound, + NeedPopulateCache(VlobID), +} + +fn cache_only_path_resolution( + ops: &WorkspaceHistoryOps, + cache: &mut super::Cache, + at: DateTime, + path_parts: &[EntryName], +) -> CacheOnlyPathResolutionOutcome { + enum StepKind<'a> { + Root, + Child(&'a ArcLocalChildManifest), + } + let cache_resolutions = match cache.resolutions.get(&at) { + Some(cache_resolutions) => cache_resolutions, + None => return CacheOnlyPathResolutionOutcome::NeedPopulateCache(ops.realm_id), + }; + + let mut last_step = StepKind::Root; + let mut parts_index = 0; + loop { + let child_name = match path_parts.get(parts_index) { + Some(part) => part, + // The path is entirely resolved ! + None => match last_step { + StepKind::Child(manifest) => { + return CacheOnlyPathResolutionOutcome::Done(manifest.to_owned()); + } + StepKind::Root => { + return match cache_resolutions.get(&ops.realm_id) { + Some(CacheResolvedEntry::Exists(manifest)) => { + CacheOnlyPathResolutionOutcome::Done(manifest.to_owned()) + } + Some(CacheResolvedEntry::NotFound) => { + CacheOnlyPathResolutionOutcome::EntryNotFound + } + None => CacheOnlyPathResolutionOutcome::NeedPopulateCache(ops.realm_id), + }; + } + }, + }; + + let parent_manifest = match &last_step { + StepKind::Root => match cache_resolutions.get(&ops.realm_id) { + Some(CacheResolvedEntry::Exists(ArcLocalChildManifest::Folder(manifest))) => { + manifest + } + // The root is always a folder + Some(CacheResolvedEntry::Exists(ArcLocalChildManifest::File(_))) => unreachable!(), + Some(CacheResolvedEntry::NotFound) => { + return CacheOnlyPathResolutionOutcome::EntryNotFound + } + None => return CacheOnlyPathResolutionOutcome::NeedPopulateCache(ops.realm_id), + }, + StepKind::Child(manifest) => match &manifest { + ArcLocalChildManifest::Folder(manifest) => manifest, + ArcLocalChildManifest::File(_) => { + // Cannot continue to resolve the path ! + return CacheOnlyPathResolutionOutcome::EntryNotFound; + } + }, + }; + let parent_id = parent_manifest.base.id; + + let child_id = match parent_manifest.children.get(child_name) { + Some(id) => *id, + None => return CacheOnlyPathResolutionOutcome::EntryNotFound, + }; + + let child_manifest = match cache_resolutions.get(&child_id) { + Some(CacheResolvedEntry::Exists(manifest)) => manifest, + Some(CacheResolvedEntry::NotFound) => { + return CacheOnlyPathResolutionOutcome::EntryNotFound + } + // Cache miss ! + // `part_index` is not incremented here, so we are going to + // leave the second loop, populate the cache, loop into first + // loop and finally re-enter the second loop with the same + // part in the path to resolve. + None => return CacheOnlyPathResolutionOutcome::NeedPopulateCache(child_id), + }; + + // Ensure the child agrees with the parent on the parenting relationship + if child_manifest.parent() != parent_id { + return CacheOnlyPathResolutionOutcome::EntryNotFound; + } + + last_step = StepKind::Child(child_manifest); + parts_index += 1; + } +} + +enum CacheOnlyRetrievalPathOutcome { + Done((ArcLocalChildManifest, FsPath)), + EntryNotFound, + NeedPopulateCache(VlobID), +} + +fn cache_only_retrieve_path_from_id( + ops: &WorkspaceHistoryOps, + cache: &mut super::Cache, + at: DateTime, + entry_id: VlobID, +) -> CacheOnlyRetrievalPathOutcome { + let cache_resolutions = match cache.resolutions.get(&at) { + Some(cache_resolutions) => cache_resolutions, + None => return CacheOnlyRetrievalPathOutcome::NeedPopulateCache(entry_id), + }; + + // Initialize the results + let mut parts = Vec::new(); + + // Get the root ID + let root_entry_id = ops.realm_id; + + let entry_manifest = match cache_resolutions.get(&entry_id) { + Some(CacheResolvedEntry::Exists(manifest)) => manifest, + Some(CacheResolvedEntry::NotFound) => return CacheOnlyRetrievalPathOutcome::EntryNotFound, + None => return CacheOnlyRetrievalPathOutcome::NeedPopulateCache(entry_id), + }; + + // Initialize the loop state + let mut current_entry_id = entry_id; + let mut current_parent_id = entry_manifest.parent(); + let mut seen: HashSet = HashSet::from_iter([current_entry_id]); + + // Loop until we reach the root + while current_entry_id != root_entry_id { + // Get the parent manifest + let parent_manifest = match cache_resolutions.get(¤t_parent_id) { + Some(CacheResolvedEntry::Exists(ArcLocalChildManifest::Folder(manifest))) => manifest, + Some(CacheResolvedEntry::Exists(ArcLocalChildManifest::File(_))) => { + return CacheOnlyRetrievalPathOutcome::EntryNotFound + } + Some(CacheResolvedEntry::NotFound) => { + return CacheOnlyRetrievalPathOutcome::EntryNotFound + } + None => return CacheOnlyRetrievalPathOutcome::NeedPopulateCache(current_parent_id), + }; + + // Update the path result + let child_name = parent_manifest.children.iter().find_map(|(name, id)| { + if *id == current_entry_id { + Some(name) + } else { + None + } + }); + match child_name { + None => return CacheOnlyRetrievalPathOutcome::EntryNotFound, + Some(child_name) => parts.push(child_name.clone()), + } + + // Update the loop state + current_entry_id = current_parent_id; + current_parent_id = parent_manifest.parent; + + // Protect against circular paths + if !seen.insert(current_entry_id) { + return CacheOnlyRetrievalPathOutcome::EntryNotFound; + } + } + + // Reverse the parts to get the path + parts.reverse(); + CacheOnlyRetrievalPathOutcome::Done((entry_manifest.to_owned(), FsPath::from_parts(parts))) +} + +/// Retrieve the path and the confinement point of a given entry ID. +pub(super) async fn retrieve_path_from_id( + ops: &WorkspaceHistoryOps, + at: DateTime, + entry_id: VlobID, +) -> Result<(ArcLocalChildManifest, FsPath), WorkspaceHistoryResolvePathError> { + loop { + // Most of the time we should have each entry in the path already in the cache, + // so we want to lock the cache once and only release it in the unlikely case + // we need to fetch from the local storage or server. + let cache_only_outcome = { + let mut cache = ops.cache.lock().expect("Mutex is poisoned"); + cache_only_retrieve_path_from_id(ops, &mut cache, at, entry_id) + }; + match cache_only_outcome { + CacheOnlyRetrievalPathOutcome::Done((entry_manifest, path)) => { + return Ok((entry_manifest, path)) + } + CacheOnlyRetrievalPathOutcome::EntryNotFound => { + return Err(WorkspaceHistoryResolvePathError::EntryNotFound) + } + // We got a cache miss + CacheOnlyRetrievalPathOutcome::NeedPopulateCache(cache_miss_entry_id) => { + populate_cache_from_server(ops, at, cache_miss_entry_id) + .await + .map_err(|err| match err { + PopulateCacheFromServerError::Offline => { + WorkspaceHistoryResolvePathError::Offline + } + PopulateCacheFromServerError::Stopped => { + WorkspaceHistoryResolvePathError::Stopped + } + PopulateCacheFromServerError::EntryNotFound => { + WorkspaceHistoryResolvePathError::EntryNotFound + } + PopulateCacheFromServerError::NoRealmAccess => { + WorkspaceHistoryResolvePathError::NoRealmAccess + } + PopulateCacheFromServerError::InvalidKeysBundle(err) => { + WorkspaceHistoryResolvePathError::InvalidKeysBundle(err) + } + PopulateCacheFromServerError::InvalidCertificate(err) => { + WorkspaceHistoryResolvePathError::InvalidCertificate(err) + } + PopulateCacheFromServerError::InvalidManifest(err) => { + WorkspaceHistoryResolvePathError::InvalidManifest(err) + } + PopulateCacheFromServerError::Internal(err) => { + err.context("cannot fetch manifest").into() + } + })?; + } + } + } +} + +pub(super) type WorkspaceHistoryGetEntryError = PopulateCacheFromServerError; + +pub(super) async fn get_entry( + ops: &WorkspaceHistoryOps, + at: DateTime, + entry_id: VlobID, +) -> Result { + // Cache lookup + + { + let cache = ops.cache.lock().expect("Mutex is poisoned"); + if let Some(cache_resolutions) = cache.resolutions.get(&at) { + if let Some(cache_resolved_entry) = cache_resolutions.get(&entry_id) { + match cache_resolved_entry { + CacheResolvedEntry::Exists(manifest) => return Ok(manifest.to_owned()), + CacheResolvedEntry::NotFound => { + return Err(WorkspaceHistoryGetEntryError::EntryNotFound) + } + } + } + } + } + + // Cache miss, must fetch from server + + populate_cache_from_server(ops, at, entry_id).await +} + +pub(super) type WorkspaceHistoryGetBlockError = FetchRemoteBlockError; + +pub(super) async fn get_block( + ops: &WorkspaceHistoryOps, + access: &BlockAccess, + remote_manifest: &FileManifest, +) -> Result { + // Cache lookup + + { + let cache = ops.cache.lock().expect("Mutex is poisoned"); + if let Some(block) = cache.blocks.get(&access.id) { + return Ok(block.to_owned()); + } + } + + // Cache miss, must fetch from server + + let block = super::super::fetch::fetch_block( + &ops.cmds, + &ops.certificates_ops, + ops.realm_id, + remote_manifest, + access, + ) + .await?; + + let mut cache = ops.cache.lock().expect("Mutex is poisoned"); + let block = match cache.blocks.entry(access.id) { + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(block.clone()); + block + } + std::collections::hash_map::Entry::Occupied(entry) => { + // Plot twist: a concurrent operation has updated the cache ! + // So we discard the data we've fetched and pretend we got a cache hit in + // the first place. + entry.get().clone() + } + }; + + Ok(block) +} diff --git a/libparsec/crates/client/src/workspace/history/stat_entry.rs b/libparsec/crates/client/src/workspace/history/stat_entry.rs new file mode 100644 index 00000000000..cd37428e1cc --- /dev/null +++ b/libparsec/crates/client/src/workspace/history/stat_entry.rs @@ -0,0 +1,160 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::ConnectionError; +use libparsec_types::prelude::*; + +use super::{WorkspaceHistoryGetEntryError, WorkspaceHistoryOps, WorkspaceHistoryResolvePathError}; +use crate::certif::{InvalidCertificateError, InvalidKeysBundleError, InvalidManifestError}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum WorkspaceHistoryEntryStat { + File { + id: VlobID, + parent: VlobID, + created: DateTime, + updated: DateTime, + version: VersionInt, + size: SizeInt, + }, + // Here Folder can also be the root of the workspace (i.e. WorkspaceManifest) + Folder { + id: VlobID, + parent: VlobID, + created: DateTime, + updated: DateTime, + version: VersionInt, + }, +} + +impl WorkspaceHistoryEntryStat { + pub fn id(&self) -> VlobID { + match self { + WorkspaceHistoryEntryStat::File { id, .. } => *id, + WorkspaceHistoryEntryStat::Folder { id, .. } => *id, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum WorkspaceHistoryStatEntryError { + #[error("Cannot reach the server")] + Offline, + #[error("Component has stopped")] + Stopped, + #[error("Path doesn't exist")] + EntryNotFound, + #[error("Not allowed to access this realm")] + NoRealmAccess, + #[error(transparent)] + InvalidKeysBundle(#[from] Box), + #[error(transparent)] + InvalidCertificate(#[from] Box), + #[error(transparent)] + InvalidManifest(#[from] Box), + #[error(transparent)] + Internal(#[from] anyhow::Error), +} + +impl From for WorkspaceHistoryStatEntryError { + fn from(value: ConnectionError) -> Self { + match value { + ConnectionError::NoResponse(_) => Self::Offline, + err => Self::Internal(err.into()), + } + } +} + +pub(crate) async fn stat_entry_by_id( + ops: &WorkspaceHistoryOps, + at: DateTime, + entry_id: VlobID, +) -> Result { + let manifest = ops.get_entry(at, entry_id).await.map_err(|err| match err { + WorkspaceHistoryGetEntryError::Offline => WorkspaceHistoryStatEntryError::Offline, + WorkspaceHistoryGetEntryError::Stopped => WorkspaceHistoryStatEntryError::Stopped, + WorkspaceHistoryGetEntryError::EntryNotFound => { + WorkspaceHistoryStatEntryError::EntryNotFound + } + WorkspaceHistoryGetEntryError::NoRealmAccess => { + WorkspaceHistoryStatEntryError::NoRealmAccess + } + WorkspaceHistoryGetEntryError::InvalidKeysBundle(err) => { + WorkspaceHistoryStatEntryError::InvalidKeysBundle(err) + } + WorkspaceHistoryGetEntryError::InvalidCertificate(err) => { + WorkspaceHistoryStatEntryError::InvalidCertificate(err) + } + WorkspaceHistoryGetEntryError::InvalidManifest(err) => { + WorkspaceHistoryStatEntryError::InvalidManifest(err) + } + WorkspaceHistoryGetEntryError::Internal(err) => err.context("cannot resolve path").into(), + })?; + + let info = match manifest { + ArcLocalChildManifest::Folder(manifest) => WorkspaceHistoryEntryStat::Folder { + id: manifest.base.id, + parent: manifest.parent, + created: manifest.base.created, + updated: manifest.updated, + version: manifest.base.version, + }, + ArcLocalChildManifest::File(manifest) => WorkspaceHistoryEntryStat::File { + id: manifest.base.id, + parent: manifest.parent, + created: manifest.base.created, + updated: manifest.updated, + version: manifest.base.version, + size: manifest.size, + }, + }; + + Ok(info) +} + +pub(crate) async fn stat_entry( + ops: &WorkspaceHistoryOps, + at: DateTime, + path: &FsPath, +) -> Result { + let manifest = ops.resolve_path(at, path).await.map_err(|err| match err { + WorkspaceHistoryResolvePathError::Offline => WorkspaceHistoryStatEntryError::Offline, + WorkspaceHistoryResolvePathError::Stopped => WorkspaceHistoryStatEntryError::Stopped, + WorkspaceHistoryResolvePathError::EntryNotFound => { + WorkspaceHistoryStatEntryError::EntryNotFound + } + WorkspaceHistoryResolvePathError::NoRealmAccess => { + WorkspaceHistoryStatEntryError::NoRealmAccess + } + WorkspaceHistoryResolvePathError::InvalidKeysBundle(err) => { + WorkspaceHistoryStatEntryError::InvalidKeysBundle(err) + } + WorkspaceHistoryResolvePathError::InvalidCertificate(err) => { + WorkspaceHistoryStatEntryError::InvalidCertificate(err) + } + WorkspaceHistoryResolvePathError::InvalidManifest(err) => { + WorkspaceHistoryStatEntryError::InvalidManifest(err) + } + WorkspaceHistoryResolvePathError::Internal(err) => { + err.context("cannot resolve path").into() + } + })?; + + let info = match manifest { + ArcLocalChildManifest::Folder(manifest) => WorkspaceHistoryEntryStat::Folder { + id: manifest.base.id, + parent: manifest.parent, + created: manifest.base.created, + updated: manifest.updated, + version: manifest.base.version, + }, + ArcLocalChildManifest::File(manifest) => WorkspaceHistoryEntryStat::File { + id: manifest.base.id, + parent: manifest.parent, + created: manifest.base.created, + updated: manifest.updated, + version: manifest.base.version, + size: manifest.size, + }, + }; + Ok(info) +} diff --git a/libparsec/crates/client/src/workspace/mod.rs b/libparsec/crates/client/src/workspace/mod.rs index cdccaa2339d..10f447a93b6 100644 --- a/libparsec/crates/client/src/workspace/mod.rs +++ b/libparsec/crates/client/src/workspace/mod.rs @@ -2,6 +2,7 @@ mod addr; mod fetch; +mod history; mod merge; mod store; mod transactions; @@ -18,6 +19,7 @@ use libparsec_types::prelude::*; use crate::{certif::CertificateOps, event_bus::EventBus, ClientConfig}; pub use addr::{WorkspaceDecryptPathAddrError, WorkspaceGeneratePathAddrError}; +pub use history::*; use store::WorkspaceStore; use transactions::RemoveEntryExpect; pub use transactions::{ @@ -118,6 +120,7 @@ pub struct WorkspaceOps { /// This contains the workspaces info that can change by uploading new /// certificates, and hence can be updated at any time. workspace_external_info: Mutex, + pub history: Arc, } impl std::panic::UnwindSafe for WorkspaceOps {} @@ -169,6 +172,13 @@ impl WorkspaceOps { ) .await?; + let history = Arc::new(WorkspaceHistoryOps::new( + config.clone(), + cmds.clone(), + certificates_ops.clone(), + realm_id, + )); + Ok(Self { config, device, @@ -185,6 +195,7 @@ impl WorkspaceOps { file_descriptors: HashMap::new(), opened_files: HashMap::new(), }), + history, }) } @@ -476,6 +487,10 @@ impl WorkspaceOps { transactions::fd_close(self, fd).await } + pub async fn fd_stat(&self, fd: FileDescriptor) -> Result { + transactions::fd_stat(self, fd).await + } + pub async fn fd_flush(&self, fd: FileDescriptor) -> Result<(), WorkspaceFdFlushError> { transactions::fd_flush(self, fd).await } @@ -508,10 +523,6 @@ impl WorkspaceOps { transactions::fd_write(self, fd, data, FdWriteStrategy::Normal { offset }).await } - pub async fn fd_stat(&self, fd: FileDescriptor) -> Result { - transactions::fd_stat(self, fd).await - } - // TODO: add `rename_entry` `move_entry` `create_folder` `create_file` `remove_entry` & `open_file` // versions that work with parent entry_id instead of path (to match more closely how FUSE works) diff --git a/libparsec/crates/client/src/workspace/store/cache.rs b/libparsec/crates/client/src/workspace/store/cache.rs index c0505923f92..0045c6b48aa 100644 --- a/libparsec/crates/client/src/workspace/store/cache.rs +++ b/libparsec/crates/client/src/workspace/store/cache.rs @@ -286,6 +286,7 @@ pub(super) async fn populate_cache_from_local_storage_or_server( &store.certificates_ops, store.realm_id, entry_id, + None, ) .await; let manifest = match outcome { diff --git a/libparsec/crates/client/tests/unit/workspace/history/fd_close.rs b/libparsec/crates/client/tests/unit/workspace/history/fd_close.rs new file mode 100644 index 00000000000..88093d1854a --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/fd_close.rs @@ -0,0 +1,82 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + test_register_sequence_of_send_hooks, test_send_hook_realm_get_keys_bundle, + test_send_hook_vlob_read_batch, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use super::super::utils::workspace_ops_factory; +use crate::workspace::WorkspaceHistoryFdCloseError; + +#[parsec_test(testbed = "minimal_client_ready")] +async fn ok(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let at = DateTime::now(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/bar.txt` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Resolve `/bar.txt` path: get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + ); + + let fd = ops + .history + .open_file(at, "/bar.txt".parse().unwrap()) + .await + .unwrap(); + ops.history.fd_close(fd).unwrap(); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn bad_file_descriptor(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + p_assert_matches!( + ops.history.fd_close(FileDescriptor(42)).unwrap_err(), + WorkspaceHistoryFdCloseError::BadFileDescriptor + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn reuse_after_close(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let at = DateTime::now(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/bar.txt` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Resolve `/bar.txt` path: get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + ); + + let fd = ops + .history + .open_file(at, "/bar.txt".parse().unwrap()) + .await + .unwrap(); + ops.history.fd_close(fd).unwrap(); + + p_assert_matches!( + ops.history.fd_close(fd).unwrap_err(), + WorkspaceHistoryFdCloseError::BadFileDescriptor + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/history/fd_read.rs b/libparsec/crates/client/tests/unit/workspace/history/fd_read.rs new file mode 100644 index 00000000000..51f7da96fc6 --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/fd_read.rs @@ -0,0 +1,294 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + protocol::authenticated_cmds, test_register_sequence_of_send_hooks, test_send_hook_block_read, + test_send_hook_realm_get_keys_bundle, test_send_hook_vlob_read_batch, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use super::super::utils::workspace_ops_factory; +use crate::workspace::WorkspaceHistoryFdReadError; + +#[parsec_test(testbed = "workspace_history")] +async fn ok(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let wksp1_bar_txt_v1_timestamp: DateTime = + *env.template.get_stuff("wksp1_bar_txt_v1_timestamp"); + let wksp1_bar_txt_v2_timestamp: DateTime = + *env.template.get_stuff("wksp1_bar_txt_v2_timestamp"); + let wksp1_bar_txt_v1_block_access: &BlockAccess = + env.template.get_stuff("wksp1_bar_txt_v1_block_access"); + let wksp1_bar_txt_v2_block1_access: &BlockAccess = + env.template.get_stuff("wksp1_bar_txt_v2_block1_access"); + let wksp1_bar_txt_v2_block2_access: &BlockAccess = + env.template.get_stuff("wksp1_bar_txt_v2_block2_access"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + // 0) Open `bar.txt` v1 and v2 + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_bar_txt_v1_timestamp, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + let fd_v1 = ops + .history + .open_file_by_id(wksp1_bar_txt_v1_timestamp, wksp1_bar_txt_id) + .await + .unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_bar_txt_v2_timestamp, wksp1_id, wksp1_bar_txt_id), + // (workspace keys bundle already fetched) + ); + + let fd_v2 = ops + .history + .open_file_by_id(wksp1_bar_txt_v2_timestamp, wksp1_bar_txt_id) + .await + .unwrap(); + + macro_rules! assert_fd_read { + ($fd:expr, $offset:expr, $size:expr, $expected:expr) => {{ + let mut buf = Vec::new(); + ops.history + .fd_read($fd, $offset, $size, &mut buf) + .await + .unwrap(); + p_assert_eq!(buf, $expected); + }}; + } + + // 1) Access bar.txt's v1 + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + test_send_hook_block_read!(env, wksp1_id, wksp1_bar_txt_v1_block_access.id), + ); + + assert_fd_read!(fd_v1, 0, 100, b"Hello v1"); + assert_fd_read!(fd_v1, 0, 1, b"H"); + assert_fd_read!(fd_v1, 4, 3, b"o v"); + assert_fd_read!(fd_v1, 10, 1, b""); + + // 2) Access bar.txt's v2 + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + test_send_hook_block_read!(env, wksp1_id, wksp1_bar_txt_v2_block1_access.id), + test_send_hook_block_read!(env, wksp1_id, wksp1_bar_txt_v2_block2_access.id), + ); + + assert_fd_read!(fd_v2, 0, 100, b"Hello v2 world"); + assert_fd_read!(fd_v2, 100, 1, b""); + assert_fd_read!(fd_v2, 10, 2, b"or"); + + ops.history.fd_close(fd_v1).unwrap(); + ops.history.fd_close(fd_v2).unwrap(); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn bad_file_descriptor(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let mut buff = vec![]; + p_assert_matches!( + ops.history + .fd_read(FileDescriptor(42), 0, 1, &mut buff) + .await + .unwrap_err(), + WorkspaceHistoryFdReadError::BadFileDescriptor + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn reuse_after_close(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let at = DateTime::now(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + let fd = ops + .history + .open_file_by_id(at, wksp1_bar_txt_id) + .await + .unwrap(); + ops.history.fd_close(fd).unwrap(); + + let mut buff = vec![]; + p_assert_matches!( + ops.history.fd_read(fd, 0, 1, &mut buff).await.unwrap_err(), + WorkspaceHistoryFdReadError::BadFileDescriptor + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn offline(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let at = DateTime::now(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + let fd = ops + .history + .open_file_by_id(at, wksp1_bar_txt_id) + .await + .unwrap(); + + let mut buff = vec![]; + p_assert_matches!( + ops.history.fd_read(fd, 0, 1, &mut buff).await.unwrap_err(), + WorkspaceHistoryFdReadError::Offline + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn stopped(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let wksp1_bar_txt_block_access: &BlockAccess = + env.template.get_stuff("wksp1_bar_txt_block_access"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let at = DateTime::now(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + let fd = ops + .history + .open_file_by_id(at, wksp1_bar_txt_id) + .await + .unwrap(); + + ops.certificates_ops.stop().await.unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Fetch the block... + test_send_hook_block_read!(env, wksp1_id, wksp1_bar_txt_block_access.id), + // ...the certificate ops is stopped so nothing more happened ! + ); + + let mut buff = vec![]; + p_assert_matches!( + ops.history.fd_read(fd, 0, 1, &mut buff).await.unwrap_err(), + WorkspaceHistoryFdReadError::Stopped + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn no_realm_access(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let wksp1_bar_txt_block_access: &BlockAccess = + env.template.get_stuff("wksp1_bar_txt_block_access"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let at = DateTime::now(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + let fd = ops + .history + .open_file_by_id(at, wksp1_bar_txt_id) + .await + .unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + move |req: authenticated_cmds::latest::block_read::Req| { + p_assert_eq!(req.block_id, wksp1_bar_txt_block_access.id); + authenticated_cmds::latest::block_read::Rep::AuthorNotAllowed + } + ); + + let mut buff = vec![]; + p_assert_matches!( + ops.history.fd_read(fd, 0, 1, &mut buff).await.unwrap_err(), + WorkspaceHistoryFdReadError::NoRealmAccess + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn block_not_found(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let wksp1_bar_txt_block_access: &BlockAccess = + env.template.get_stuff("wksp1_bar_txt_block_access"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let at = DateTime::now(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + let fd = ops + .history + .open_file_by_id(at, wksp1_bar_txt_id) + .await + .unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + move |req: authenticated_cmds::latest::block_read::Req| { + p_assert_eq!(req.block_id, wksp1_bar_txt_block_access.id); + authenticated_cmds::latest::block_read::Rep::BlockNotFound + } + ); + + let mut buff = vec![]; + p_assert_matches!( + ops.history.fd_read(fd, 0, 1, &mut buff).await.unwrap_err(), + WorkspaceHistoryFdReadError::BlockNotFound + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/history/fd_stat.rs b/libparsec/crates/client/tests/unit/workspace/history/fd_stat.rs new file mode 100644 index 00000000000..867243333af --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/fd_stat.rs @@ -0,0 +1,137 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + test_register_sequence_of_send_hooks, test_send_hook_realm_get_keys_bundle, + test_send_hook_vlob_read_batch, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use super::super::utils::workspace_ops_factory; +use crate::workspace::{WorkspaceHistoryFdStatError, WorkspaceHistoryFileStat}; + +#[parsec_test(testbed = "workspace_history")] +async fn ok(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let wksp1_bar_txt_v1_timestamp: DateTime = + *env.template.get_stuff("wksp1_bar_txt_v1_timestamp"); + let wksp1_bar_txt_v2_timestamp: DateTime = + *env.template.get_stuff("wksp1_bar_txt_v2_timestamp"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + // 0) Open `bar.txt` v1 and v2 + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_bar_txt_v1_timestamp, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + let fd_v1 = ops + .history + .open_file_by_id(wksp1_bar_txt_v1_timestamp, wksp1_bar_txt_id) + .await + .unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_bar_txt_v2_timestamp, wksp1_id, wksp1_bar_txt_id), + // (workspace keys bundle already fetched) + ); + + let fd_v2 = ops + .history + .open_file_by_id(wksp1_bar_txt_v2_timestamp, wksp1_bar_txt_id) + .await + .unwrap(); + + // 1) Access bar.txt's v1 + + p_assert_matches!( + ops.history.fd_stat(fd_v1).await.unwrap(), + WorkspaceHistoryFileStat { + id, + created, + updated, + version, + size, + } if { + p_assert_eq!(id, wksp1_bar_txt_id); + p_assert_eq!(created, "2000-01-10T00:00:00Z".parse().unwrap()); + p_assert_eq!(updated, "2000-01-10T00:00:00Z".parse().unwrap()); + p_assert_eq!(version, 1); + p_assert_eq!(size, 8); + true + } + ); + + // 2) Access bar.txt's v2 + + p_assert_matches!( + ops.history.fd_stat(fd_v2).await.unwrap(), + WorkspaceHistoryFileStat { + id, + created, + updated, + version, + size, + } if { + p_assert_eq!(id, wksp1_bar_txt_id); + p_assert_eq!(created, "2000-01-18T00:00:00Z".parse().unwrap()); + p_assert_eq!(updated, "2000-01-18T00:00:00Z".parse().unwrap()); + p_assert_eq!(version, 2); + p_assert_eq!(size, 14); + true + } + ); + + ops.history.fd_close(fd_v1).unwrap(); + ops.history.fd_close(fd_v2).unwrap(); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn bad_file_descriptor(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + p_assert_matches!( + ops.history.fd_stat(FileDescriptor(42)).await.unwrap_err(), + WorkspaceHistoryFdStatError::BadFileDescriptor + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn reuse_after_close(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let at = DateTime::now(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + let fd = ops + .history + .open_file_by_id(at, wksp1_bar_txt_id) + .await + .unwrap(); + ops.history.fd_close(fd).unwrap(); + + p_assert_matches!( + ops.history.fd_stat(fd).await.unwrap_err(), + WorkspaceHistoryFdStatError::BadFileDescriptor + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/history/get_workspace_manifest_v1_timestamp.rs b/libparsec/crates/client/tests/unit/workspace/history/get_workspace_manifest_v1_timestamp.rs new file mode 100644 index 00000000000..2fe85875927 --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/get_workspace_manifest_v1_timestamp.rs @@ -0,0 +1,208 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + protocol::authenticated_cmds, test_register_send_hook, test_register_sequence_of_send_hooks, + test_send_hook_realm_get_keys_bundle, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use crate::workspace::WorkspaceHistoryGetWorkspaceManifestV1TimestampError; + +use super::super::utils::workspace_ops_factory; + +#[parsec_test(testbed = "minimal_client_ready")] +async fn v1_exists(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + // Get back last workspace manifest version synced in server + let (wksp1_v1_remote_manifest, wksp1_v1_encrypted) = env + .template + .events + .iter() + .find_map(|e| match e { + TestbedEvent::CreateOrUpdateFolderManifestVlob(e) + if e.manifest.id == wksp1_id && e.manifest.version == 1 => + { + Some((e.manifest.clone(), e.encrypted(&env.template))) + } + _ => None, + }) + .unwrap(); + let wksp1_v1_timestamp = wksp1_v1_remote_manifest.timestamp; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Read the workspace manifest's vlob v1 + { + let last_realm_certificate_timestamp = + env.get_last_realm_certificate_timestamp(wksp1_id); + let last_common_certificate_timestamp = env.get_last_common_certificate_timestamp(); + move |req: authenticated_cmds::latest::vlob_read_versions::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.items, [(wksp1_id, 1)]); + authenticated_cmds::latest::vlob_read_versions::Rep::Ok { + items: vec![( + wksp1_id, + 1, + wksp1_v1_remote_manifest.author, + wksp1_v1_remote_manifest.version, + wksp1_v1_remote_manifest.timestamp, + wksp1_v1_encrypted, + )], + needed_common_certificate_timestamp: last_common_certificate_timestamp, + needed_realm_certificate_timestamp: last_realm_certificate_timestamp, + } + } + }, + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + p_assert_eq!( + ops.history + .get_workspace_manifest_v1_timestamp() + .await + .unwrap(), + Some(wksp1_v1_timestamp) + ); + + // The result is in cache so no more request should be needed now + p_assert_eq!( + ops.history + .get_workspace_manifest_v1_timestamp() + .await + .unwrap(), + Some(wksp1_v1_timestamp) + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn still_in_v0(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_send_hook(&env.discriminant_dir, { + let last_realm_certificate_timestamp = env.get_last_realm_certificate_timestamp(wksp1_id); + let last_common_certificate_timestamp = env.get_last_common_certificate_timestamp(); + move |req: authenticated_cmds::latest::vlob_read_versions::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.items, [(wksp1_id, 1)]); + authenticated_cmds::latest::vlob_read_versions::Rep::Ok { + items: vec![], + needed_common_certificate_timestamp: last_common_certificate_timestamp, + needed_realm_certificate_timestamp: last_realm_certificate_timestamp, + } + } + }); + + p_assert_eq!( + ops.history + .get_workspace_manifest_v1_timestamp() + .await + .unwrap(), + None + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn offline(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + p_assert_matches!( + ops.history + .get_workspace_manifest_v1_timestamp() + .await + .unwrap_err(), + WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Offline + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn stopped(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + ops.certificates_ops.stop().await.unwrap(); + + // Get back last workspace manifest version synced in server + let (wksp1_v1_remote_manifest, wksp1_v1_encrypted) = env + .template + .events + .iter() + .find_map(|e| match e { + TestbedEvent::CreateOrUpdateFolderManifestVlob(e) + if e.manifest.id == wksp1_id && e.manifest.version == 1 => + { + Some((e.manifest.clone(), e.encrypted(&env.template))) + } + _ => None, + }) + .unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the workspace manifest's vlob v1... + { + let last_realm_certificate_timestamp = + env.get_last_realm_certificate_timestamp(wksp1_id); + let last_common_certificate_timestamp = env.get_last_common_certificate_timestamp(); + move |req: authenticated_cmds::latest::vlob_read_versions::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.items, [(wksp1_id, 1)]); + authenticated_cmds::latest::vlob_read_versions::Rep::Ok { + items: vec![( + wksp1_id, + 1, + wksp1_v1_remote_manifest.author, + wksp1_v1_remote_manifest.version, + wksp1_v1_remote_manifest.timestamp, + wksp1_v1_encrypted, + )], + needed_common_certificate_timestamp: last_common_certificate_timestamp, + needed_realm_certificate_timestamp: last_realm_certificate_timestamp, + } + } + }, + // ...should be checking the workspace manifest, but the certificate ops + // is stopped so nothing more happened ! + ); + + p_assert_matches!( + ops.history + .get_workspace_manifest_v1_timestamp() + .await + .unwrap_err(), + WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Stopped + ); +} + +#[parsec_test(testbed = "coolorg")] +async fn no_realm_access(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let mallory = env.local_device("mallory@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &mallory, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + move |req: authenticated_cmds::latest::vlob_read_versions::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.items, [(wksp1_id, 1)]); + authenticated_cmds::latest::vlob_read_versions::Rep::AuthorNotAllowed + } + ); + + p_assert_matches!( + ops.history + .get_workspace_manifest_v1_timestamp() + .await + .unwrap_err(), + WorkspaceHistoryGetWorkspaceManifestV1TimestampError::NoRealmAccess + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/history/mod.rs b/libparsec/crates/client/tests/unit/workspace/history/mod.rs new file mode 100644 index 00000000000..bff42af2ffe --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/mod.rs @@ -0,0 +1,12 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +mod fd_close; +mod fd_read; +mod fd_stat; +mod get_workspace_manifest_v1_timestamp; +mod open_file; +mod open_file_by_id; +mod stat_entry; +mod stat_entry_by_id; +mod stat_folder_children; +mod stat_folder_children_by_id; diff --git a/libparsec/crates/client/tests/unit/workspace/history/open_file.rs b/libparsec/crates/client/tests/unit/workspace/history/open_file.rs new file mode 100644 index 00000000000..33df1d916bb --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/open_file.rs @@ -0,0 +1,209 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + protocol::authenticated_cmds, test_register_sequence_of_send_hooks, + test_send_hook_realm_get_keys_bundle, test_send_hook_vlob_read_batch, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use super::super::utils::workspace_ops_factory; +use crate::workspace::WorkspaceHistoryOpenFileError; + +#[parsec_test(testbed = "minimal_client_ready")] +async fn ok(#[values("open", "open_and_get_id")] kind: &str, env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/bar.txt` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Resolve `/bar.txt` path: get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + ); + + match kind { + "open" => { + let outcome = ops.history.open_file(at, "/bar.txt".parse().unwrap()).await; + p_assert_matches!( + outcome, + Ok(fd) if fd.0 == 1 + ); + } + "open_and_get_id" => { + let outcome = ops + .history + .open_file_and_get_id(at, "/bar.txt".parse().unwrap()) + .await; + p_assert_matches!( + outcome, + Ok((fd, entry_id)) if fd.0 == 1 && entry_id == wksp1_bar_txt_id + ) + } + unknown => panic!("Unknown kind: {}", unknown), + } +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn offline(#[values("open", "open_and_get_id")] kind: &str, env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let outcome = match kind { + "open" => ops + .history + .open_file(DateTime::now(), "/bar.txt".parse().unwrap()) + .await + .unwrap_err(), + "open_and_get_id" => ops + .history + .open_file_and_get_id(DateTime::now(), "/bar.txt".parse().unwrap()) + .await + .unwrap_err(), + unknown => panic!("Unknown kind: {}", unknown), + }; + p_assert_matches!(outcome, WorkspaceHistoryOpenFileError::Offline); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn stopped(#[values("open", "open_and_get_id")] kind: &str, env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + ops.certificates_ops.stop().await.unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/bar.txt` path: get back the workspace manifest... + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // ...should be checking the workspace manifest, but the certificate ops + // is stopped so nothing more happened ! + ); + + let outcome = match kind { + "open" => ops + .history + .open_file(at, "/bar.txt".parse().unwrap()) + .await + .unwrap_err(), + "open_and_get_id" => ops + .history + .open_file_and_get_id(at, "/bar.txt".parse().unwrap()) + .await + .unwrap_err(), + unknown => panic!("Unknown kind: {}", unknown), + }; + p_assert_matches!(outcome, WorkspaceHistoryOpenFileError::Stopped); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn no_realm_access(#[values("open", "open_and_get_id")] kind: &str, env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/bar.txt` path: get back the workspace manifest, but fail ! + move |req: authenticated_cmds::latest::vlob_read_batch::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.at, Some(at)); + p_assert_eq!(req.vlobs, [wksp1_id]); + authenticated_cmds::latest::vlob_read_batch::Rep::AuthorNotAllowed + } + ); + + let outcome = match kind { + "open" => ops + .history + .open_file(at, "/bar.txt".parse().unwrap()) + .await + .unwrap_err(), + "open_and_get_id" => ops + .history + .open_file_and_get_id(at, "/bar.txt".parse().unwrap()) + .await + .unwrap_err(), + unknown => panic!("Unknown kind: {}", unknown), + }; + p_assert_matches!(outcome, WorkspaceHistoryOpenFileError::NoRealmAccess); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_not_found(#[values("open", "open_and_get_id")] kind: &str, env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/dummy.txt` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // Nothing more since `/dummy.txt` is not in the workspace manifest + ); + + let outcome = match kind { + "open" => ops + .history + .open_file(at, "/dummy.txt".parse().unwrap()) + .await + .unwrap_err(), + "open_and_get_id" => ops + .history + .open_file_and_get_id(at, "/dummy.txt".parse().unwrap()) + .await + .unwrap_err(), + unknown => panic!("Unknown kind: {}", unknown), + }; + p_assert_matches!(outcome, WorkspaceHistoryOpenFileError::EntryNotFound); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_not_a_file(#[values("open", "open_and_get_id")] kind: &str, env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_id: VlobID = *env.template.get_stuff("wksp1_foo_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/foo` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Resolve `/foo` path: get back the `foo` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_foo_id), + ); + + let outcome = match kind { + "open" => ops + .history + .open_file(at, "/foo".parse().unwrap()) + .await + .unwrap_err(), + "open_and_get_id" => ops + .history + .open_file_and_get_id(at, "/foo".parse().unwrap()) + .await + .unwrap_err(), + unknown => panic!("Unknown kind: {}", unknown), + }; + p_assert_matches!( + outcome, + WorkspaceHistoryOpenFileError::EntryNotAFile { entry_id } if entry_id == wksp1_foo_id + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/history/open_file_by_id.rs b/libparsec/crates/client/tests/unit/workspace/history/open_file_by_id.rs new file mode 100644 index 00000000000..e7355505ef4 --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/open_file_by_id.rs @@ -0,0 +1,157 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + protocol::authenticated_cmds, test_register_sequence_of_send_hooks, + test_send_hook_realm_get_keys_bundle, test_send_hook_vlob_read_batch, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use super::super::utils::workspace_ops_factory; +use crate::workspace::WorkspaceHistoryOpenFileError; + +#[parsec_test(testbed = "minimal_client_ready")] +async fn ok(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back `/bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + p_assert_matches!( + ops.history.open_file_by_id(at, wksp1_bar_txt_id).await, + Ok(fd) if fd.0 == 1 + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn offline(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let outcome = ops + .history + .open_file_by_id(DateTime::now(), wksp1_bar_txt_id) + .await + .unwrap_err(); + p_assert_matches!(outcome, WorkspaceHistoryOpenFileError::Offline); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn stopped(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + ops.certificates_ops.stop().await.unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back `/bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + // ...should be checking the manifest, but the certificate ops is stopped ! + ); + + let outcome = ops + .history + .open_file_by_id(at, wksp1_bar_txt_id) + .await + .unwrap_err(); + p_assert_matches!(outcome, WorkspaceHistoryOpenFileError::Stopped); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn no_realm_access(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Try to get back `/bar.txt` manifest, but fail ! + move |req: authenticated_cmds::latest::vlob_read_batch::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.at, Some(at)); + p_assert_eq!(req.vlobs, [wksp1_bar_txt_id]); + authenticated_cmds::latest::vlob_read_batch::Rep::AuthorNotAllowed + } + ); + + let outcome = ops + .history + .open_file_by_id(at, wksp1_bar_txt_id) + .await + .unwrap_err(); + p_assert_matches!(outcome, WorkspaceHistoryOpenFileError::NoRealmAccess); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_not_found(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let dummy_id = VlobID::default(); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + let last_common_certificate_timestamp = env.get_last_common_certificate_timestamp(); + let last_realm_certificate_timestamp = env.get_last_realm_certificate_timestamp(wksp1_id); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // Try to get dummy ID from the server + move |req: authenticated_cmds::latest::vlob_read_batch::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.at, Some(at)); + p_assert_eq!(req.vlobs, [dummy_id]); + authenticated_cmds::latest::vlob_read_batch::Rep::Ok { + items: vec![], + needed_common_certificate_timestamp: last_common_certificate_timestamp, + needed_realm_certificate_timestamp: last_realm_certificate_timestamp, + } + } + ); + + let outcome = ops.history.open_file_by_id(at, dummy_id).await.unwrap_err(); + p_assert_matches!(outcome, WorkspaceHistoryOpenFileError::EntryNotFound); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_not_a_file(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_id: VlobID = *env.template.get_stuff("wksp1_foo_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back `/foo` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_foo_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + let outcome = ops + .history + .open_file_by_id(at, wksp1_foo_id) + .await + .unwrap_err(); + p_assert_matches!( + outcome, + WorkspaceHistoryOpenFileError::EntryNotAFile { entry_id } if entry_id == wksp1_foo_id + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/history/stat_entry.rs b/libparsec/crates/client/tests/unit/workspace/history/stat_entry.rs new file mode 100644 index 00000000000..20ea9b2e0e6 --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/stat_entry.rs @@ -0,0 +1,234 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + protocol::authenticated_cmds, test_register_sequence_of_send_hooks, + test_send_hook_realm_get_keys_bundle, test_send_hook_vlob_read_batch, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use super::super::utils::workspace_ops_factory; +use crate::workspace::{WorkspaceHistoryEntryStat, WorkspaceHistoryStatEntryError}; + +#[parsec_test(testbed = "workspace_history")] +async fn ok_folder(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_v2_children_available_timestamp: DateTime = *env + .template + .get_stuff("wksp1_foo_v2_children_available_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + p_assert_matches!( + ops.history.stat_entry(wksp1_foo_v2_children_available_timestamp, &"/".parse().unwrap()).await, + Ok(WorkspaceHistoryEntryStat::Folder{ + id, + parent, + created, + updated, + version, + }) + if { + p_assert_eq!(id, wksp1_id); + p_assert_eq!(parent, wksp1_id); + p_assert_eq!(created, "2000-01-12T00:00:00Z".parse().unwrap()); + p_assert_eq!(updated, "2000-01-12T00:00:00Z".parse().unwrap()); + p_assert_eq!(version, 2); + true + } + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn ok_file(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let wksp1_foo_v2_children_available_timestamp: DateTime = *env + .template + .get_stuff("wksp1_foo_v2_children_available_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/bar.txt` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Resolve `/bar.txt` path: get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, wksp1_bar_txt_id), + ); + + p_assert_matches!( + ops.history.stat_entry(wksp1_foo_v2_children_available_timestamp, &"/bar.txt".parse().unwrap()).await, + Ok(WorkspaceHistoryEntryStat::File{ + id, + parent, + created, + updated, + version, + size, + }) + if { + p_assert_eq!(id, wksp1_bar_txt_id); + p_assert_eq!(parent, wksp1_id); + p_assert_eq!(created, "2000-01-18T00:00:00Z".parse().unwrap()); + p_assert_eq!(updated, "2000-01-18T00:00:00Z".parse().unwrap()); + p_assert_eq!(version, 2); + p_assert_eq!(size, 14); + true + } + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn before_realm_exists(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_created_timestamp: DateTime = *env.template.get_stuff("wksp1_created_timestamp"); + let at_before = wksp1_created_timestamp.add_us(-1000); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest, but at this time + // the realm does not exist yet ! + test_send_hook_vlob_read_batch!(env, at: at_before, wksp1_id, wksp1_id), + // No need to fetch workspace keys bundle since there is no vlob to decrypt ! + ); + + p_assert_matches!( + ops.history + .stat_entry(at_before, &"/".parse().unwrap()) + .await, + Err(WorkspaceHistoryStatEntryError::EntryNotFound) + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn before_workspace_manifest_v1(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bootstrapped_timestamp: DateTime = + *env.template.get_stuff("wksp1_bootstrapped_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Try to get back the workspace manifest... but fail since it doesn't exist yet ! + test_send_hook_vlob_read_batch!(env, at: wksp1_bootstrapped_timestamp, wksp1_id, wksp1_id), + ); + + p_assert_matches!( + ops.history + .stat_entry(wksp1_bootstrapped_timestamp, &"/".parse().unwrap()) + .await, + Err(WorkspaceHistoryStatEntryError::EntryNotFound) + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn offline(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + p_assert_matches!( + ops.history + .stat_entry(at, &"/".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatEntryError::Offline + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn stopped(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + ops.certificates_ops.stop().await.unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest... + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // ...should be checking the workspace manifest, but the certificate ops + // is stopped so nothing more happened ! + ); + + p_assert_matches!( + ops.history + .stat_entry(at, &"/".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatEntryError::Stopped + ); +} + +#[parsec_test(testbed = "coolorg")] +async fn no_realm_access(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let mallory = env.local_device("mallory@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &mallory, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest, but fail ! + move |req: authenticated_cmds::latest::vlob_read_batch::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.at, Some(at)); + p_assert_eq!(req.vlobs, [wksp1_id]); + authenticated_cmds::latest::vlob_read_batch::Rep::AuthorNotAllowed + } + ); + + p_assert_matches!( + ops.history + .stat_entry(at, &"/".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatEntryError::NoRealmAccess + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_not_found(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/dummy` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // Nothing more since `/dummy` is not in the workspace manifest + ); + + p_assert_matches!( + ops.history + .stat_entry(at, &"/dummy".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatEntryError::EntryNotFound + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/history/stat_entry_by_id.rs b/libparsec/crates/client/tests/unit/workspace/history/stat_entry_by_id.rs new file mode 100644 index 00000000000..4f00d445822 --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/stat_entry_by_id.rs @@ -0,0 +1,228 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + protocol::authenticated_cmds, test_register_sequence_of_send_hooks, + test_send_hook_realm_get_keys_bundle, test_send_hook_vlob_read_batch, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use super::super::utils::workspace_ops_factory; +use crate::workspace::{WorkspaceHistoryEntryStat, WorkspaceHistoryStatEntryError}; + +#[parsec_test(testbed = "workspace_history")] +async fn ok_folder(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_v2_children_available_timestamp: DateTime = *env + .template + .get_stuff("wksp1_foo_v2_children_available_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + p_assert_matches!( + ops.history.stat_entry_by_id(wksp1_foo_v2_children_available_timestamp, wksp1_id).await, + Ok(WorkspaceHistoryEntryStat::Folder{ + id, + parent, + created, + updated, + version, + }) + if { + p_assert_eq!(id, wksp1_id); + p_assert_eq!(parent, wksp1_id); + p_assert_eq!(created, "2000-01-12T00:00:00Z".parse().unwrap()); + p_assert_eq!(updated, "2000-01-12T00:00:00Z".parse().unwrap()); + p_assert_eq!(version, 2); + true + } + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn ok_file(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + let wksp1_foo_v2_children_available_timestamp: DateTime = *env + .template + .get_stuff("wksp1_foo_v2_children_available_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + p_assert_matches!( + ops.history.stat_entry_by_id(wksp1_foo_v2_children_available_timestamp, wksp1_bar_txt_id).await, + Ok(WorkspaceHistoryEntryStat::File{ + id, + parent, + created, + updated, + version, + size, + }) + if { + p_assert_eq!(id, wksp1_bar_txt_id); + p_assert_eq!(parent, wksp1_id); + p_assert_eq!(created, "2000-01-18T00:00:00Z".parse().unwrap()); + p_assert_eq!(updated, "2000-01-18T00:00:00Z".parse().unwrap()); + p_assert_eq!(version, 2); + p_assert_eq!(size, 14); + true + } + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn before_realm_exists(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_created_timestamp: DateTime = *env.template.get_stuff("wksp1_created_timestamp"); + let at_before = wksp1_created_timestamp.add_us(-1000); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the workspace manifest, but at this time + // the realm does not exist yet ! + test_send_hook_vlob_read_batch!(env, at: at_before, wksp1_id, wksp1_id), + // No need to fetch workspace keys bundle since there is no vlob to decrypt ! + ); + + p_assert_matches!( + ops.history.stat_entry_by_id(at_before, wksp1_id).await, + Err(WorkspaceHistoryStatEntryError::EntryNotFound) + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn before_workspace_manifest_v1(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bootstrapped_timestamp: DateTime = + *env.template.get_stuff("wksp1_bootstrapped_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Try to get back the workspace manifest... but fail since it doesn't exist yet ! + test_send_hook_vlob_read_batch!(env, at: wksp1_bootstrapped_timestamp, wksp1_id, wksp1_id), + ); + + p_assert_matches!( + ops.history + .stat_entry_by_id(wksp1_bootstrapped_timestamp, wksp1_id) + .await, + Err(WorkspaceHistoryStatEntryError::EntryNotFound) + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn offline(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + p_assert_matches!( + ops.history + .stat_entry_by_id(at, wksp1_id) + .await + .unwrap_err(), + WorkspaceHistoryStatEntryError::Offline + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn stopped(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + ops.certificates_ops.stop().await.unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the workspace manifest... + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // ...should be checking the workspace manifest, but the certificate ops + // is stopped so nothing more happened ! + ); + + p_assert_matches!( + ops.history + .stat_entry_by_id(at, wksp1_id) + .await + .unwrap_err(), + WorkspaceHistoryStatEntryError::Stopped + ); +} + +#[parsec_test(testbed = "coolorg")] +async fn no_realm_access(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let mallory = env.local_device("mallory@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &mallory, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the workspace manifest, but fail ! + move |req: authenticated_cmds::latest::vlob_read_batch::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.at, Some(at)); + p_assert_eq!(req.vlobs, [wksp1_id]); + authenticated_cmds::latest::vlob_read_batch::Rep::AuthorNotAllowed + } + ); + + p_assert_matches!( + ops.history + .stat_entry_by_id(at, wksp1_id) + .await + .unwrap_err(), + WorkspaceHistoryStatEntryError::NoRealmAccess + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_not_found(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let dummy_id = VlobID::default(); + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Try to get back the dummy manifest... and fail since it doesn't exist ! + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, dummy_id), + ); + + p_assert_matches!( + ops.history + .stat_entry_by_id(at, dummy_id) + .await + .unwrap_err(), + WorkspaceHistoryStatEntryError::EntryNotFound + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/history/stat_folder_children.rs b/libparsec/crates/client/tests/unit/workspace/history/stat_folder_children.rs new file mode 100644 index 00000000000..ea68a654671 --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/stat_folder_children.rs @@ -0,0 +1,317 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + protocol::authenticated_cmds, test_register_sequence_of_send_hooks, + test_send_hook_realm_get_keys_bundle, test_send_hook_vlob_read_batch, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use super::super::utils::workspace_ops_factory; +use crate::workspace::{WorkspaceHistoryEntryStat, WorkspaceHistoryStatFolderChildrenError}; + +// Note `open_folder_reader` is not directly tested since it is internally used by `stat_folder_children` + +#[parsec_test(testbed = "workspace_history")] +async fn ok(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_id: VlobID = *env.template.get_stuff("wksp1_foo_id"); + let wksp1_foo_egg_txt_id: VlobID = *env.template.get_stuff("wksp1_foo_egg_txt_id"); + let wksp1_foo_spam_id: VlobID = *env.template.get_stuff("wksp1_foo_spam_id"); + let wksp1_foo_v2_children_available_timestamp: DateTime = *env + .template + .get_stuff("wksp1_foo_v2_children_available_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Resolve `/foo` path: get back the `foo` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, wksp1_foo_id), + // 4) Get `/foo/egg.txt` & `/foo/spam` manifest (order of fetch is not guaranteed) + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, allowed: [wksp1_foo_egg_txt_id, wksp1_foo_spam_id]), + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, allowed: [wksp1_foo_egg_txt_id, wksp1_foo_spam_id]), + ); + + let mut children_stats = ops + .history + .stat_folder_children( + wksp1_foo_v2_children_available_timestamp, + &"/foo".parse().unwrap(), + ) + .await + .unwrap(); + children_stats.sort_by(|a, b| a.0.cmp(&b.0)); + p_assert_eq!( + children_stats, + vec![ + ( + "egg.txt".parse().unwrap(), + WorkspaceHistoryEntryStat::File { + id: wksp1_foo_egg_txt_id, + parent: wksp1_foo_id, + created: "2000-01-20T00:00:00Z".parse().unwrap(), + updated: "2000-01-20T00:00:00Z".parse().unwrap(), + version: 1, + size: 0, + } + ), + ( + "spam".parse().unwrap(), + WorkspaceHistoryEntryStat::Folder { + id: wksp1_foo_spam_id, + parent: wksp1_foo_id, + created: "2000-01-21T00:00:00Z".parse().unwrap(), + updated: "2000-01-21T00:00:00Z".parse().unwrap(), + version: 1, + } + ), + ] + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn before_realm_exists(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_created_timestamp: DateTime = *env.template.get_stuff("wksp1_created_timestamp"); + let at_before = wksp1_created_timestamp.add_us(-1000); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest, but at this time + // the realm does not exist yet ! + test_send_hook_vlob_read_batch!(env, at: at_before, wksp1_id, wksp1_id), + // No need to fetch workspace keys bundle since there is no vlob to decrypt ! + ); + + p_assert_matches!( + ops.history + .stat_folder_children(at_before, &"/".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::EntryNotFound + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn before_workspace_manifest_v1(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bootstrapped_timestamp: DateTime = + *env.template.get_stuff("wksp1_bootstrapped_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Try to get back the workspace manifest... but fail since it doesn't exist yet ! + test_send_hook_vlob_read_batch!(env, at: wksp1_bootstrapped_timestamp, wksp1_id, wksp1_id), + ); + + p_assert_matches!( + ops.history + .stat_folder_children(wksp1_bootstrapped_timestamp, &"/".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::EntryNotFound + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn entry_available_but_not_referenced_in_parent(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_v1_timestamp: DateTime = *env.template.get_stuff("wksp1_foo_v1_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v1_timestamp, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + p_assert_eq!( + ops.history + .stat_folder_children(wksp1_foo_v1_timestamp, &"/".parse().unwrap()) + .await + .unwrap(), + vec![] + ); + p_assert_matches!( + ops.history + .stat_folder_children(wksp1_foo_v1_timestamp, &"/foo".parse().unwrap()) + .await, + Err(WorkspaceHistoryStatFolderChildrenError::EntryNotFound) + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn entry_available_but_not_its_children(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_id: VlobID = *env.template.get_stuff("wksp1_foo_id"); + let wksp1_foo_spam_id: VlobID = *env.template.get_stuff("wksp1_foo_spam_id"); + let wksp1_foo_egg_txt_id: VlobID = *env.template.get_stuff("wksp1_foo_egg_txt_id"); + let wksp1_foo_v2_timestamp: DateTime = *env.template.get_stuff("wksp1_foo_v2_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_timestamp, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Resolve `/foo` path: get back the `foo` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_timestamp, wksp1_id, wksp1_foo_id), + // 4) Get `/foo/egg.txt` and `/foo/spam` manifest, which didn't exist at that time. + // Note there is no guarantee on the order of those requests. + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_timestamp, wksp1_id, allowed: [wksp1_foo_egg_txt_id, wksp1_foo_spam_id]), + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_timestamp, wksp1_id, allowed: [wksp1_foo_egg_txt_id, wksp1_foo_spam_id]), + ); + + p_assert_eq!( + ops.history + .stat_folder_children(wksp1_foo_v2_timestamp, &"/foo".parse().unwrap()) + .await + .unwrap(), + vec![] + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn offline(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + p_assert_matches!( + ops.history + .stat_folder_children(at, &"/".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::Offline + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn stopped(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + ops.certificates_ops.stop().await.unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest... + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // ...should be checking the workspace manifest, but the certificate ops + // is stopped so nothing more happened ! + ); + + p_assert_matches!( + ops.history + .stat_folder_children(at, &"/".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::Stopped + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn no_realm_access(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/` path: get back the workspace manifest, but fail ! + move |req: authenticated_cmds::latest::vlob_read_batch::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.at, Some(at)); + p_assert_eq!(req.vlobs, [wksp1_id]); + authenticated_cmds::latest::vlob_read_batch::Rep::AuthorNotAllowed + } + ); + + p_assert_matches!( + ops.history + .stat_folder_children(at, &"/".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::NoRealmAccess + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_not_found(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/dummy` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // Nothing more since `/dummy` is not in the workspace manifest + ); + + p_assert_matches!( + ops.history + .stat_folder_children(at, &"/dummy".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::EntryNotFound + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_is_file(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/bar.txt` path: get back the workspace manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Resolve `/bar.txt` path: get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + ); + + p_assert_matches!( + ops.history + .stat_folder_children(at, &"/bar.txt".parse().unwrap()) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::EntryIsFile + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/history/stat_folder_children_by_id.rs b/libparsec/crates/client/tests/unit/workspace/history/stat_folder_children_by_id.rs new file mode 100644 index 00000000000..e6a56db4c27 --- /dev/null +++ b/libparsec/crates/client/tests/unit/workspace/history/stat_folder_children_by_id.rs @@ -0,0 +1,300 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use libparsec_client_connection::{ + protocol::authenticated_cmds, test_register_sequence_of_send_hooks, + test_send_hook_realm_get_keys_bundle, test_send_hook_vlob_read_batch, +}; +use libparsec_tests_fixtures::prelude::*; +use libparsec_types::prelude::*; + +use super::super::utils::workspace_ops_factory; +use crate::workspace::{WorkspaceHistoryEntryStat, WorkspaceHistoryStatFolderChildrenError}; + +// Note `open_folder_reader_by_id` is not directly tested since it is internally used by `stat_folder_children_by_id` + +#[parsec_test(testbed = "workspace_history")] +async fn ok(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_id: VlobID = *env.template.get_stuff("wksp1_foo_id"); + let wksp1_foo_egg_txt_id: VlobID = *env.template.get_stuff("wksp1_foo_egg_txt_id"); + let wksp1_foo_spam_id: VlobID = *env.template.get_stuff("wksp1_foo_spam_id"); + let wksp1_foo_v2_children_available_timestamp: DateTime = *env + .template + .get_stuff("wksp1_foo_v2_children_available_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `foo` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, wksp1_foo_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Get `/foo/egg.txt` & `/foo/spam` manifest (order of fetch is not guaranteed) + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, allowed: [wksp1_foo_egg_txt_id, wksp1_foo_spam_id]), + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_children_available_timestamp, wksp1_id, allowed: [wksp1_foo_egg_txt_id, wksp1_foo_spam_id]), + ); + + let mut children_stats = ops + .history + .stat_folder_children_by_id(wksp1_foo_v2_children_available_timestamp, wksp1_foo_id) + .await + .unwrap(); + children_stats.sort_by(|a, b| a.0.cmp(&b.0)); + p_assert_eq!( + children_stats, + vec![ + ( + "egg.txt".parse().unwrap(), + WorkspaceHistoryEntryStat::File { + id: wksp1_foo_egg_txt_id, + parent: wksp1_foo_id, + created: "2000-01-20T00:00:00Z".parse().unwrap(), + updated: "2000-01-20T00:00:00Z".parse().unwrap(), + version: 1, + size: 0, + } + ), + ( + "spam".parse().unwrap(), + WorkspaceHistoryEntryStat::Folder { + id: wksp1_foo_spam_id, + parent: wksp1_foo_id, + created: "2000-01-21T00:00:00Z".parse().unwrap(), + updated: "2000-01-21T00:00:00Z".parse().unwrap(), + version: 1, + } + ), + ] + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn before_realm_exists(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_created_timestamp: DateTime = *env.template.get_stuff("wksp1_created_timestamp"); + let at_before = wksp1_created_timestamp.add_us(-1000); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the workspace manifest, but at this time the realm does not exist yet ! + test_send_hook_vlob_read_batch!(env, at: at_before, wksp1_id, wksp1_id), + // No need to fetch workspace keys bundle since there is no vlob to decrypt ! + ); + + p_assert_matches!( + ops.history + .stat_folder_children_by_id(at_before, wksp1_id) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::EntryNotFound + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn before_workspace_manifest_v1(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bootstrapped_timestamp: DateTime = + *env.template.get_stuff("wksp1_bootstrapped_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Try to get back the workspace manifest... but fail since it doesn't exist yet ! + test_send_hook_vlob_read_batch!(env, at: wksp1_bootstrapped_timestamp, wksp1_id, wksp1_id), + ); + + p_assert_matches!( + ops.history + .stat_folder_children_by_id(wksp1_bootstrapped_timestamp, wksp1_id) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::EntryNotFound + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn entry_available_but_not_referenced_in_parent(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_id: VlobID = *env.template.get_stuff("wksp1_foo_id"); + let wksp1_foo_v1_timestamp: DateTime = *env.template.get_stuff("wksp1_foo_v1_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `foo` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v1_timestamp, wksp1_id, wksp1_foo_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + p_assert_eq!( + ops.history + .stat_folder_children_by_id(wksp1_foo_v1_timestamp, wksp1_foo_id) + .await + .unwrap(), + vec![] + ); +} + +#[parsec_test(testbed = "workspace_history")] +async fn entry_available_but_not_its_children(env: &TestbedEnv) { + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_foo_id: VlobID = *env.template.get_stuff("wksp1_foo_id"); + let wksp1_foo_spam_id: VlobID = *env.template.get_stuff("wksp1_foo_spam_id"); + let wksp1_foo_egg_txt_id: VlobID = *env.template.get_stuff("wksp1_foo_egg_txt_id"); + let wksp1_foo_v2_timestamp: DateTime = *env.template.get_stuff("wksp1_foo_v2_timestamp"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id.to_owned()).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Resolve `/foo` path: get back the `foo` manifest + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_timestamp, wksp1_id, wksp1_foo_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + // 3) Get `/foo/egg.txt` and `/foo/spam` manifest, which didn't exist at that time. + // Note there is no guarantee on the order of those requests. + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_timestamp, wksp1_id, allowed: [wksp1_foo_egg_txt_id, wksp1_foo_spam_id]), + test_send_hook_vlob_read_batch!(env, at: wksp1_foo_v2_timestamp, wksp1_id, allowed: [wksp1_foo_egg_txt_id, wksp1_foo_spam_id]), + ); + + p_assert_eq!( + ops.history + .stat_folder_children_by_id(wksp1_foo_v2_timestamp, wksp1_foo_id) + .await + .unwrap(), + vec![] + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn offline(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + p_assert_matches!( + ops.history + .stat_folder_children_by_id(at, wksp1_id) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::Offline + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn stopped(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + ops.certificates_ops.stop().await.unwrap(); + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the workspace manifest... + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_id), + // ...should be checking the workspace manifest, but the certificate ops + // is stopped so nothing more happened ! + ); + + p_assert_matches!( + ops.history + .stat_folder_children_by_id(at, wksp1_id) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::Stopped + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn no_realm_access(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the workspace manifest, but fail ! + move |req: authenticated_cmds::latest::vlob_read_batch::Req| { + p_assert_eq!(req.realm_id, wksp1_id); + p_assert_eq!(req.at, Some(at)); + p_assert_eq!(req.vlobs, [wksp1_id]); + authenticated_cmds::latest::vlob_read_batch::Rep::AuthorNotAllowed + } + ); + + p_assert_matches!( + ops.history + .stat_folder_children_by_id(at, wksp1_id) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::NoRealmAccess + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_not_found(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let dummy_id = VlobID::default(); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Try to get back the dummy manifest... and fail since it doesn't exist ! + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, dummy_id), + ); + + p_assert_matches!( + ops.history + .stat_folder_children_by_id(at, dummy_id) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::EntryNotFound + ); +} + +#[parsec_test(testbed = "minimal_client_ready")] +async fn entry_is_file(env: &TestbedEnv) { + let at = DateTime::now(); + let wksp1_id: VlobID = *env.template.get_stuff("wksp1_id"); + let wksp1_bar_txt_id: VlobID = *env.template.get_stuff("wksp1_bar_txt_id"); + + let alice = env.local_device("alice@dev1"); + let ops = workspace_ops_factory(&env.discriminant_dir, &alice, wksp1_id).await; + + test_register_sequence_of_send_hooks!( + &env.discriminant_dir, + // 1) Get back the `bar.txt` manifest + test_send_hook_vlob_read_batch!(env, at: at, wksp1_id, wksp1_bar_txt_id), + // 2) Fetch workspace keys bundle to decrypt the vlob + test_send_hook_realm_get_keys_bundle!(env, alice.user_id, wksp1_id), + ); + + p_assert_matches!( + ops.history + .stat_folder_children_by_id(at, wksp1_bar_txt_id) + .await + .unwrap_err(), + WorkspaceHistoryStatFolderChildrenError::EntryIsFile + ); +} diff --git a/libparsec/crates/client/tests/unit/workspace/mod.rs b/libparsec/crates/client/tests/unit/workspace/mod.rs index 9dea02b7c07..73894f77b88 100644 --- a/libparsec/crates/client/tests/unit/workspace/mod.rs +++ b/libparsec/crates/client/tests/unit/workspace/mod.rs @@ -14,6 +14,7 @@ mod fd_write; mod file_operations; mod file_operations_stateful; mod folder_transactions; +mod history; mod inbound_sync_file; mod inbound_sync_folder; mod inbound_sync_root; From 30065d9496f0f450b4ebb63777dc4a49e8f1e76a Mon Sep 17 00:00:00 2001 From: Emmanuel Leblond Date: Fri, 18 Oct 2024 20:44:59 +0200 Subject: [PATCH 5/8] Expose workspace history in libparsec bindings --- bindings/generator/api/__init__.py | 1 + bindings/generator/api/workspace_history.py | 282 ++++++++++++++++++++ libparsec/src/lib.rs | 2 + libparsec/src/workspace_history.rs | 132 +++++++++ 4 files changed, 417 insertions(+) create mode 100644 bindings/generator/api/workspace_history.py create mode 100644 libparsec/src/workspace_history.rs diff --git a/bindings/generator/api/__init__.py b/bindings/generator/api/__init__.py index c552e624180..1e95d529095 100644 --- a/bindings/generator/api/__init__.py +++ b/bindings/generator/api/__init__.py @@ -14,3 +14,4 @@ from .testbed import * from .validation import * from .workspace import * +from .workspace_history import * diff --git a/bindings/generator/api/workspace_history.py b/bindings/generator/api/workspace_history.py new file mode 100644 index 00000000000..a84433f2622 --- /dev/null +++ b/bindings/generator/api/workspace_history.py @@ -0,0 +1,282 @@ +# Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +from typing import Optional + +from .common import ( + U64, + DateTime, + EntryName, + ErrorVariant, + FsPath, + Handle, + Ref, + Result, + SizeInt, + Variant, + VersionInt, + VlobID, + Structure, +) +from .workspace import FileDescriptor + + +# +# Workspace history FS operations +# + + +class WorkspaceHistoryGetWorkspaceManifestV1TimestampError(ErrorVariant): + class Offline: + pass + + class Stopped: + pass + + class NoRealmAccess: + pass + + class InvalidKeysBundle: + pass + + class InvalidCertificate: + pass + + class InvalidManifest: + pass + + class Internal: + pass + + +async def workspace_history_get_workspace_manifest_v1_timestamp( + workspace: Handle, +) -> Result[Optional[DateTime], WorkspaceHistoryGetWorkspaceManifestV1TimestampError]: + raise NotImplementedError + + +class WorkspaceHistoryEntryStat(Variant): + class File: + id: VlobID + parent: VlobID + created: DateTime + updated: DateTime + version: VersionInt + size: SizeInt + + class Folder: + id: VlobID + parent: VlobID + created: DateTime + updated: DateTime + version: VersionInt + + +class WorkspaceHistoryStatEntryError(ErrorVariant): + class Offline: + pass + + class Stopped: + pass + + class EntryNotFound: + pass + + class NoRealmAccess: + pass + + class InvalidKeysBundle: + pass + + class InvalidCertificate: + pass + + class InvalidManifest: + pass + + class Internal: + pass + + +async def workspace_history_stat_entry( + workspace: Handle, + at: DateTime, + path: Ref[FsPath], +) -> Result[WorkspaceHistoryEntryStat, WorkspaceHistoryStatEntryError]: + raise NotImplementedError + + +async def workspace_history_stat_entry_by_id( + workspace: Handle, + at: DateTime, + entry_id: VlobID, +) -> Result[WorkspaceHistoryEntryStat, WorkspaceHistoryStatEntryError]: + raise NotImplementedError + + +class WorkspaceHistoryStatFolderChildrenError(ErrorVariant): + class Offline: + pass + + class Stopped: + pass + + class EntryNotFound: + pass + + class EntryIsFile: + pass + + class NoRealmAccess: + pass + + class InvalidKeysBundle: + pass + + class InvalidCertificate: + pass + + class InvalidManifest: + pass + + class Internal: + pass + + +async def workspace_history_stat_folder_children( + workspace: Handle, + at: DateTime, + path: Ref[FsPath], +) -> Result[ + list[tuple[EntryName, WorkspaceHistoryEntryStat]], WorkspaceHistoryStatFolderChildrenError +]: + raise NotImplementedError + + +async def workspace_history_stat_folder_children_by_id( + workspace: Handle, + at: DateTime, + entry_id: VlobID, +) -> Result[ + list[tuple[EntryName, WorkspaceHistoryEntryStat]], WorkspaceHistoryStatFolderChildrenError +]: + raise NotImplementedError + + +class WorkspaceHistoryOpenFileError(ErrorVariant): + class Offline: + pass + + class Stopped: + pass + + class NoRealmAccess: + pass + + class EntryNotFound: + pass + + class EntryNotAFile: + pass + + class InvalidKeysBundle: + pass + + class InvalidCertificate: + pass + + class InvalidManifest: + pass + + class Internal: + pass + + +async def workspace_history_open_file( + workspace: Handle, + at: DateTime, + path: FsPath, +) -> Result[FileDescriptor, WorkspaceHistoryOpenFileError]: + raise NotImplementedError + + +async def workspace_history_open_file_by_id( + workspace: Handle, + at: DateTime, + entry_id: VlobID, +) -> Result[FileDescriptor, WorkspaceHistoryOpenFileError]: + raise NotImplementedError + + +class WorkspaceHistoryFdCloseError(ErrorVariant): + class BadFileDescriptor: + pass + + class Internal: + pass + + +def workspace_history_fd_close( + workspace: Handle, fd: FileDescriptor +) -> Result[None, WorkspaceHistoryFdCloseError]: + raise NotImplementedError + + +class WorkspaceHistoryFdReadError(ErrorVariant): + class Offline: + pass + + class Stopped: + pass + + class BadFileDescriptor: + pass + + class NoRealmAccess: + pass + + class BlockNotFound: + pass + + class InvalidBlockAccess: + pass + + class InvalidKeysBundle: + pass + + class InvalidCertificate: + pass + + class Internal: + pass + + +async def workspace_history_fd_read( + workspace: Handle, + fd: FileDescriptor, + offset: U64, + size: U64, +) -> Result[bytes, WorkspaceHistoryFdReadError]: + raise NotImplementedError + + +class WorkspaceHistoryFdStatError(ErrorVariant): + class BadFileDescriptor: + pass + + class Internal: + pass + + +class WorkspaceHistoryFileStat(Structure): + id: VlobID + created: DateTime + updated: DateTime + version: VersionInt + size: SizeInt + + +async def workspace_history_fd_stat( + workspace: Handle, + fd: FileDescriptor, +) -> Result[WorkspaceHistoryFileStat, WorkspaceHistoryFdStatError]: + raise NotImplementedError diff --git a/libparsec/src/lib.rs b/libparsec/src/lib.rs index fe30714ad94..9a4b027c2a1 100644 --- a/libparsec/src/lib.rs +++ b/libparsec/src/lib.rs @@ -15,6 +15,7 @@ mod platform; mod testbed; mod validation; mod workspace; +mod workspace_history; pub use addr::*; pub use cancel::*; @@ -36,6 +37,7 @@ pub use platform::*; pub use testbed::*; pub use validation::*; pub use workspace::*; +pub use workspace_history::*; pub mod internal { pub use libparsec_client::{ diff --git a/libparsec/src/workspace_history.rs b/libparsec/src/workspace_history.rs new file mode 100644 index 00000000000..8c6ca773c20 --- /dev/null +++ b/libparsec/src/workspace_history.rs @@ -0,0 +1,132 @@ +// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS + +use std::sync::Arc; + +pub use libparsec_client::workspace::{ + WorkspaceHistoryEntryStat, WorkspaceHistoryFdCloseError, WorkspaceHistoryFdReadError, + WorkspaceHistoryFdStatError, WorkspaceHistoryFileStat, + WorkspaceHistoryGetWorkspaceManifestV1TimestampError, WorkspaceHistoryOpenFileError, + WorkspaceHistoryStatEntryError, WorkspaceHistoryStatFolderChildrenError, +}; +use libparsec_types::prelude::*; + +use crate::handle::{borrow_from_handle, Handle, HandleItem}; + +fn borrow_workspace(workspace: Handle) -> anyhow::Result> { + borrow_from_handle(workspace, |x| match x { + HandleItem::Workspace { workspace_ops, .. } => Some(workspace_ops.clone()), + _ => None, + }) +} + +/* + * Workspace history FS operations + */ + +pub async fn workspace_history_get_workspace_manifest_v1_timestamp( + workspace: Handle, +) -> Result, WorkspaceHistoryGetWorkspaceManifestV1TimestampError> { + let workspace = borrow_workspace(workspace)?; + + workspace + .history + .get_workspace_manifest_v1_timestamp() + .await +} + +pub async fn workspace_history_stat_entry( + workspace: Handle, + at: DateTime, + path: &FsPath, +) -> Result { + let workspace = borrow_workspace(workspace)?; + + workspace.history.stat_entry(at, path).await +} + +pub async fn workspace_history_stat_entry_by_id( + workspace: Handle, + at: DateTime, + entry_id: VlobID, +) -> Result { + let workspace = borrow_workspace(workspace)?; + + workspace.history.stat_entry_by_id(at, entry_id).await +} + +pub async fn workspace_history_stat_folder_children( + workspace: Handle, + at: DateTime, + path: &FsPath, +) -> Result, WorkspaceHistoryStatFolderChildrenError> { + let workspace = borrow_workspace(workspace)?; + + workspace.history.stat_folder_children(at, path).await +} + +pub async fn workspace_history_stat_folder_children_by_id( + workspace: Handle, + at: DateTime, + entry_id: VlobID, +) -> Result, WorkspaceHistoryStatFolderChildrenError> { + let workspace = borrow_workspace(workspace)?; + + workspace + .history + .stat_folder_children_by_id(at, entry_id) + .await +} + +pub async fn workspace_history_open_file( + workspace: Handle, + at: DateTime, + path: FsPath, +) -> Result { + let workspace = borrow_workspace(workspace)?; + + workspace.history.open_file(at, path).await +} + +pub async fn workspace_history_open_file_by_id( + workspace: Handle, + at: DateTime, + entry_id: VlobID, +) -> Result { + let workspace = borrow_workspace(workspace)?; + + workspace.history.open_file_by_id(at, entry_id).await +} + +pub fn workspace_history_fd_close( + workspace: Handle, + fd: FileDescriptor, +) -> Result<(), WorkspaceHistoryFdCloseError> { + let workspace = borrow_workspace(workspace)?; + + workspace.history.fd_close(fd) +} + +pub async fn workspace_history_fd_read( + workspace: Handle, + fd: FileDescriptor, + offset: u64, + size: u64, +) -> Result, WorkspaceHistoryFdReadError> { + let workspace = borrow_workspace(workspace)?; + + let mut buf = Vec::with_capacity(size as usize); + workspace + .history + .fd_read(fd, offset, size, &mut buf) + .await?; + Ok(buf) +} + +pub async fn workspace_history_fd_stat( + workspace: Handle, + fd: FileDescriptor, +) -> Result { + let workspace = borrow_workspace(workspace)?; + + workspace.history.fd_stat(fd).await +} From 45fda6791aa302d5a5b6253061cbf5627c7404a1 Mon Sep 17 00:00:00 2001 From: Emmanuel Leblond Date: Fri, 18 Oct 2024 20:42:56 +0200 Subject: [PATCH 6/8] Re-generate electron&web bindings --- bindings/electron/src/index.d.ts | 337 ++ bindings/electron/src/meths.rs | 5847 ++++++++++++------- bindings/web/src/meths.rs | 3585 ++++++++---- client/src/plugins/libparsec/definitions.ts | 400 ++ 4 files changed, 6904 insertions(+), 3265 deletions(-) diff --git a/bindings/electron/src/index.d.ts b/bindings/electron/src/index.d.ts index 0d8937cfc6a..286ac3052d4 100644 --- a/bindings/electron/src/index.d.ts +++ b/bindings/electron/src/index.d.ts @@ -269,6 +269,15 @@ export interface UserInfo { } +export interface WorkspaceHistoryFileStat { + id: string + created: number + updated: number + version: number + size: number +} + + export interface WorkspaceInfo { id: string currentName: string @@ -1717,6 +1726,287 @@ export type WorkspaceGeneratePathAddrError = | WorkspaceGeneratePathAddrErrorStopped +// WorkspaceHistoryEntryStat +export interface WorkspaceHistoryEntryStatFile { + tag: "File" + id: string + parent: string + created: number + updated: number + version: number + size: number +} +export interface WorkspaceHistoryEntryStatFolder { + tag: "Folder" + id: string + parent: string + created: number + updated: number + version: number +} +export type WorkspaceHistoryEntryStat = + | WorkspaceHistoryEntryStatFile + | WorkspaceHistoryEntryStatFolder + + +// WorkspaceHistoryFdCloseError +export interface WorkspaceHistoryFdCloseErrorBadFileDescriptor { + tag: "BadFileDescriptor" + error: string +} +export interface WorkspaceHistoryFdCloseErrorInternal { + tag: "Internal" + error: string +} +export type WorkspaceHistoryFdCloseError = + | WorkspaceHistoryFdCloseErrorBadFileDescriptor + | WorkspaceHistoryFdCloseErrorInternal + + +// WorkspaceHistoryFdReadError +export interface WorkspaceHistoryFdReadErrorBadFileDescriptor { + tag: "BadFileDescriptor" + error: string +} +export interface WorkspaceHistoryFdReadErrorBlockNotFound { + tag: "BlockNotFound" + error: string +} +export interface WorkspaceHistoryFdReadErrorInternal { + tag: "Internal" + error: string +} +export interface WorkspaceHistoryFdReadErrorInvalidBlockAccess { + tag: "InvalidBlockAccess" + error: string +} +export interface WorkspaceHistoryFdReadErrorInvalidCertificate { + tag: "InvalidCertificate" + error: string +} +export interface WorkspaceHistoryFdReadErrorInvalidKeysBundle { + tag: "InvalidKeysBundle" + error: string +} +export interface WorkspaceHistoryFdReadErrorNoRealmAccess { + tag: "NoRealmAccess" + error: string +} +export interface WorkspaceHistoryFdReadErrorOffline { + tag: "Offline" + error: string +} +export interface WorkspaceHistoryFdReadErrorStopped { + tag: "Stopped" + error: string +} +export type WorkspaceHistoryFdReadError = + | WorkspaceHistoryFdReadErrorBadFileDescriptor + | WorkspaceHistoryFdReadErrorBlockNotFound + | WorkspaceHistoryFdReadErrorInternal + | WorkspaceHistoryFdReadErrorInvalidBlockAccess + | WorkspaceHistoryFdReadErrorInvalidCertificate + | WorkspaceHistoryFdReadErrorInvalidKeysBundle + | WorkspaceHistoryFdReadErrorNoRealmAccess + | WorkspaceHistoryFdReadErrorOffline + | WorkspaceHistoryFdReadErrorStopped + + +// WorkspaceHistoryFdStatError +export interface WorkspaceHistoryFdStatErrorBadFileDescriptor { + tag: "BadFileDescriptor" + error: string +} +export interface WorkspaceHistoryFdStatErrorInternal { + tag: "Internal" + error: string +} +export type WorkspaceHistoryFdStatError = + | WorkspaceHistoryFdStatErrorBadFileDescriptor + | WorkspaceHistoryFdStatErrorInternal + + +// WorkspaceHistoryGetWorkspaceManifestV1TimestampError +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInternal { + tag: "Internal" + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidCertificate { + tag: "InvalidCertificate" + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidKeysBundle { + tag: "InvalidKeysBundle" + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidManifest { + tag: "InvalidManifest" + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorNoRealmAccess { + tag: "NoRealmAccess" + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorOffline { + tag: "Offline" + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorStopped { + tag: "Stopped" + error: string +} +export type WorkspaceHistoryGetWorkspaceManifestV1TimestampError = + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInternal + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidCertificate + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidKeysBundle + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidManifest + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorNoRealmAccess + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorOffline + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorStopped + + +// WorkspaceHistoryOpenFileError +export interface WorkspaceHistoryOpenFileErrorEntryNotAFile { + tag: "EntryNotAFile" + error: string +} +export interface WorkspaceHistoryOpenFileErrorEntryNotFound { + tag: "EntryNotFound" + error: string +} +export interface WorkspaceHistoryOpenFileErrorInternal { + tag: "Internal" + error: string +} +export interface WorkspaceHistoryOpenFileErrorInvalidCertificate { + tag: "InvalidCertificate" + error: string +} +export interface WorkspaceHistoryOpenFileErrorInvalidKeysBundle { + tag: "InvalidKeysBundle" + error: string +} +export interface WorkspaceHistoryOpenFileErrorInvalidManifest { + tag: "InvalidManifest" + error: string +} +export interface WorkspaceHistoryOpenFileErrorNoRealmAccess { + tag: "NoRealmAccess" + error: string +} +export interface WorkspaceHistoryOpenFileErrorOffline { + tag: "Offline" + error: string +} +export interface WorkspaceHistoryOpenFileErrorStopped { + tag: "Stopped" + error: string +} +export type WorkspaceHistoryOpenFileError = + | WorkspaceHistoryOpenFileErrorEntryNotAFile + | WorkspaceHistoryOpenFileErrorEntryNotFound + | WorkspaceHistoryOpenFileErrorInternal + | WorkspaceHistoryOpenFileErrorInvalidCertificate + | WorkspaceHistoryOpenFileErrorInvalidKeysBundle + | WorkspaceHistoryOpenFileErrorInvalidManifest + | WorkspaceHistoryOpenFileErrorNoRealmAccess + | WorkspaceHistoryOpenFileErrorOffline + | WorkspaceHistoryOpenFileErrorStopped + + +// WorkspaceHistoryStatEntryError +export interface WorkspaceHistoryStatEntryErrorEntryNotFound { + tag: "EntryNotFound" + error: string +} +export interface WorkspaceHistoryStatEntryErrorInternal { + tag: "Internal" + error: string +} +export interface WorkspaceHistoryStatEntryErrorInvalidCertificate { + tag: "InvalidCertificate" + error: string +} +export interface WorkspaceHistoryStatEntryErrorInvalidKeysBundle { + tag: "InvalidKeysBundle" + error: string +} +export interface WorkspaceHistoryStatEntryErrorInvalidManifest { + tag: "InvalidManifest" + error: string +} +export interface WorkspaceHistoryStatEntryErrorNoRealmAccess { + tag: "NoRealmAccess" + error: string +} +export interface WorkspaceHistoryStatEntryErrorOffline { + tag: "Offline" + error: string +} +export interface WorkspaceHistoryStatEntryErrorStopped { + tag: "Stopped" + error: string +} +export type WorkspaceHistoryStatEntryError = + | WorkspaceHistoryStatEntryErrorEntryNotFound + | WorkspaceHistoryStatEntryErrorInternal + | WorkspaceHistoryStatEntryErrorInvalidCertificate + | WorkspaceHistoryStatEntryErrorInvalidKeysBundle + | WorkspaceHistoryStatEntryErrorInvalidManifest + | WorkspaceHistoryStatEntryErrorNoRealmAccess + | WorkspaceHistoryStatEntryErrorOffline + | WorkspaceHistoryStatEntryErrorStopped + + +// WorkspaceHistoryStatFolderChildrenError +export interface WorkspaceHistoryStatFolderChildrenErrorEntryIsFile { + tag: "EntryIsFile" + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorEntryNotFound { + tag: "EntryNotFound" + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorInternal { + tag: "Internal" + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorInvalidCertificate { + tag: "InvalidCertificate" + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorInvalidKeysBundle { + tag: "InvalidKeysBundle" + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorInvalidManifest { + tag: "InvalidManifest" + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorNoRealmAccess { + tag: "NoRealmAccess" + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorOffline { + tag: "Offline" + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorStopped { + tag: "Stopped" + error: string +} +export type WorkspaceHistoryStatFolderChildrenError = + | WorkspaceHistoryStatFolderChildrenErrorEntryIsFile + | WorkspaceHistoryStatFolderChildrenErrorEntryNotFound + | WorkspaceHistoryStatFolderChildrenErrorInternal + | WorkspaceHistoryStatFolderChildrenErrorInvalidCertificate + | WorkspaceHistoryStatFolderChildrenErrorInvalidKeysBundle + | WorkspaceHistoryStatFolderChildrenErrorInvalidManifest + | WorkspaceHistoryStatFolderChildrenErrorNoRealmAccess + | WorkspaceHistoryStatFolderChildrenErrorOffline + | WorkspaceHistoryStatFolderChildrenErrorStopped + + // WorkspaceInfoError export interface WorkspaceInfoErrorInternal { tag: "Internal" @@ -2446,6 +2736,53 @@ export function workspaceGeneratePathAddr( workspace: number, path: string ): Promise> +export function workspaceHistoryFdClose( + workspace: number, + fd: number +): Promise> +export function workspaceHistoryFdRead( + workspace: number, + fd: number, + offset: number, + size: number +): Promise> +export function workspaceHistoryFdStat( + workspace: number, + fd: number +): Promise> +export function workspaceHistoryGetWorkspaceManifestV1Timestamp( + workspace: number +): Promise> +export function workspaceHistoryOpenFile( + workspace: number, + at: number, + path: string +): Promise> +export function workspaceHistoryOpenFileById( + workspace: number, + at: number, + entry_id: string +): Promise> +export function workspaceHistoryStatEntry( + workspace: number, + at: number, + path: string +): Promise> +export function workspaceHistoryStatEntryById( + workspace: number, + at: number, + entry_id: string +): Promise> +export function workspaceHistoryStatFolderChildren( + workspace: number, + at: number, + path: string +): Promise, WorkspaceHistoryStatFolderChildrenError>> +export function workspaceHistoryStatFolderChildrenById( + workspace: number, + at: number, + entry_id: string +): Promise, WorkspaceHistoryStatFolderChildrenError>> export function workspaceInfo( workspace: number ): Promise> diff --git a/bindings/electron/src/meths.rs b/bindings/electron/src/meths.rs index 38752bee64f..789637191c5 100644 --- a/bindings/electron/src/meths.rs +++ b/bindings/electron/src/meths.rs @@ -2382,6 +2382,127 @@ fn struct_user_info_rs_to_js<'a>( Ok(js_obj) } +// WorkspaceHistoryFileStat + +#[allow(dead_code)] +fn struct_workspace_history_file_stat_js_to_rs<'a>( + cx: &mut impl Context<'a>, + obj: Handle<'a, JsObject>, +) -> NeonResult { + let id = { + let js_val: Handle = obj.get(cx, "id")?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let created = { + let js_val: Handle = obj.get(cx, "created")?; + { + let v = js_val.value(cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let updated = { + let js_val: Handle = obj.get(cx, "updated")?; + { + let v = js_val.value(cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let version = { + let js_val: Handle = obj.get(cx, "version")?; + { + let v = js_val.value(cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let size = { + let js_val: Handle = obj.get(cx, "size")?; + { + let v = js_val.value(cx); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + cx.throw_type_error("Not an u64 number")? + } + let v = v as u64; + v + } + }; + Ok(libparsec::WorkspaceHistoryFileStat { + id, + created, + updated, + version, + size, + }) +} + +#[allow(dead_code)] +fn struct_workspace_history_file_stat_rs_to_js<'a>( + cx: &mut impl Context<'a>, + rs_obj: libparsec::WorkspaceHistoryFileStat, +) -> NeonResult> { + let js_obj = cx.empty_object(); + let js_id = JsString::try_new(cx, { + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(rs_obj.id) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(cx)?; + js_obj.set(cx, "id", js_id)?; + let js_created = JsNumber::new(cx, { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + match custom_to_rs_f64(rs_obj.created) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }); + js_obj.set(cx, "created", js_created)?; + let js_updated = JsNumber::new(cx, { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + match custom_to_rs_f64(rs_obj.updated) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }); + js_obj.set(cx, "updated", js_updated)?; + let js_version = JsNumber::new(cx, rs_obj.version as f64); + js_obj.set(cx, "version", js_version)?; + let js_size = JsNumber::new(cx, rs_obj.size as f64); + js_obj.set(cx, "size", js_size)?; + Ok(js_obj) +} + // WorkspaceInfo #[allow(dead_code)] @@ -6458,507 +6579,1846 @@ fn variant_workspace_generate_path_addr_error_rs_to_js<'a>( Ok(js_obj) } -// WorkspaceInfoError +// WorkspaceHistoryEntryStat #[allow(dead_code)] -fn variant_workspace_info_error_rs_to_js<'a>( +fn variant_workspace_history_entry_stat_js_to_rs<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceInfoError, -) -> NeonResult> { - let js_obj = cx.empty_object(); - let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; - js_obj.set(cx, "error", js_display)?; - match rs_obj { - libparsec::WorkspaceInfoError::Internal { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceInfoErrorInternal").or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; + obj: Handle<'a, JsObject>, +) -> NeonResult { + let tag = obj.get::(cx, "tag")?.value(cx); + match tag.as_str() { + "WorkspaceHistoryEntryStatFile" => { + let id = { + let js_val: Handle = obj.get(cx, "id")?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let parent = { + let js_val: Handle = obj.get(cx, "parent")?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let created = { + let js_val: Handle = obj.get(cx, "created")?; + { + let v = js_val.value(cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let updated = { + let js_val: Handle = obj.get(cx, "updated")?; + { + let v = js_val.value(cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let version = { + let js_val: Handle = obj.get(cx, "version")?; + { + let v = js_val.value(cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let size = { + let js_val: Handle = obj.get(cx, "size")?; + { + let v = js_val.value(cx); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + cx.throw_type_error("Not an u64 number")? + } + let v = v as u64; + v + } + }; + Ok(libparsec::WorkspaceHistoryEntryStat::File { + id, + parent, + created, + updated, + version, + size, + }) + } + "WorkspaceHistoryEntryStatFolder" => { + let id = { + let js_val: Handle = obj.get(cx, "id")?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let parent = { + let js_val: Handle = obj.get(cx, "parent")?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let created = { + let js_val: Handle = obj.get(cx, "created")?; + { + let v = js_val.value(cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let updated = { + let js_val: Handle = obj.get(cx, "updated")?; + { + let v = js_val.value(cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let version = { + let js_val: Handle = obj.get(cx, "version")?; + { + let v = js_val.value(cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + Ok(libparsec::WorkspaceHistoryEntryStat::Folder { + id, + parent, + created, + updated, + version, + }) } + _ => cx.throw_type_error("Object is not a WorkspaceHistoryEntryStat"), } - Ok(js_obj) } -// WorkspaceMountError - #[allow(dead_code)] -fn variant_workspace_mount_error_rs_to_js<'a>( +fn variant_workspace_history_entry_stat_rs_to_js<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceMountError, + rs_obj: libparsec::WorkspaceHistoryEntryStat, ) -> NeonResult> { let js_obj = cx.empty_object(); - let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; - js_obj.set(cx, "error", js_display)?; match rs_obj { - libparsec::WorkspaceMountError::Disabled { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceMountErrorDisabled").or_throw(cx)?; + libparsec::WorkspaceHistoryEntryStat::File { + id, + parent, + created, + updated, + version, + size, + .. + } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryEntryStatFile").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; + let js_id = JsString::try_new(cx, { + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(id) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(cx)?; + js_obj.set(cx, "id", js_id)?; + let js_parent = JsString::try_new(cx, { + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(parent) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(cx)?; + js_obj.set(cx, "parent", js_parent)?; + let js_created = JsNumber::new(cx, { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + match custom_to_rs_f64(created) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }); + js_obj.set(cx, "created", js_created)?; + let js_updated = JsNumber::new(cx, { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + match custom_to_rs_f64(updated) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }); + js_obj.set(cx, "updated", js_updated)?; + let js_version = JsNumber::new(cx, version as f64); + js_obj.set(cx, "version", js_version)?; + let js_size = JsNumber::new(cx, size as f64); + js_obj.set(cx, "size", js_size)?; } - libparsec::WorkspaceMountError::Internal { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceMountErrorInternal").or_throw(cx)?; + libparsec::WorkspaceHistoryEntryStat::Folder { + id, + parent, + created, + updated, + version, + .. + } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryEntryStatFolder").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; + let js_id = JsString::try_new(cx, { + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(id) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(cx)?; + js_obj.set(cx, "id", js_id)?; + let js_parent = JsString::try_new(cx, { + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(parent) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(cx)?; + js_obj.set(cx, "parent", js_parent)?; + let js_created = JsNumber::new(cx, { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + match custom_to_rs_f64(created) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }); + js_obj.set(cx, "created", js_created)?; + let js_updated = JsNumber::new(cx, { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + match custom_to_rs_f64(updated) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }); + js_obj.set(cx, "updated", js_updated)?; + let js_version = JsNumber::new(cx, version as f64); + js_obj.set(cx, "version", js_version)?; } } Ok(js_obj) } -// WorkspaceMoveEntryError +// WorkspaceHistoryFdCloseError #[allow(dead_code)] -fn variant_workspace_move_entry_error_rs_to_js<'a>( +fn variant_workspace_history_fd_close_error_rs_to_js<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceMoveEntryError, + rs_obj: libparsec::WorkspaceHistoryFdCloseError, ) -> NeonResult> { let js_obj = cx.empty_object(); let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; js_obj.set(cx, "error", js_display)?; match rs_obj { - libparsec::WorkspaceMoveEntryError::CannotMoveRoot { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceMoveEntryErrorCannotMoveRoot").or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; - } - libparsec::WorkspaceMoveEntryError::DestinationExists { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceMoveEntryErrorDestinationExists").or_throw(cx)?; + libparsec::WorkspaceHistoryFdCloseError::BadFileDescriptor { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryFdCloseErrorBadFileDescriptor") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::DestinationNotFound { .. } => { + libparsec::WorkspaceHistoryFdCloseError::Internal { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceMoveEntryErrorDestinationNotFound").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryFdCloseErrorInternal").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::Internal { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceMoveEntryErrorInternal").or_throw(cx)?; + } + Ok(js_obj) +} + +// WorkspaceHistoryFdReadError + +#[allow(dead_code)] +fn variant_workspace_history_fd_read_error_rs_to_js<'a>( + cx: &mut impl Context<'a>, + rs_obj: libparsec::WorkspaceHistoryFdReadError, +) -> NeonResult> { + let js_obj = cx.empty_object(); + let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; + js_obj.set(cx, "error", js_display)?; + match rs_obj { + libparsec::WorkspaceHistoryFdReadError::BadFileDescriptor { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryFdReadErrorBadFileDescriptor") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::InvalidCertificate { .. } => { + libparsec::WorkspaceHistoryFdReadError::BlockNotFound { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceMoveEntryErrorInvalidCertificate").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryFdReadErrorBlockNotFound").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::InvalidKeysBundle { .. } => { + libparsec::WorkspaceHistoryFdReadError::Internal { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceMoveEntryErrorInvalidKeysBundle").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryFdReadErrorInternal").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::InvalidManifest { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceMoveEntryErrorInvalidManifest").or_throw(cx)?; + libparsec::WorkspaceHistoryFdReadError::InvalidBlockAccess { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryFdReadErrorInvalidBlockAccess") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::NoRealmAccess { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceMoveEntryErrorNoRealmAccess").or_throw(cx)?; + libparsec::WorkspaceHistoryFdReadError::InvalidCertificate { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryFdReadErrorInvalidCertificate") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::Offline { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceMoveEntryErrorOffline").or_throw(cx)?; + libparsec::WorkspaceHistoryFdReadError::InvalidKeysBundle { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryFdReadErrorInvalidKeysBundle") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::ReadOnlyRealm { .. } => { + libparsec::WorkspaceHistoryFdReadError::NoRealmAccess { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceMoveEntryErrorReadOnlyRealm").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryFdReadErrorNoRealmAccess").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::SourceNotFound { .. } => { + libparsec::WorkspaceHistoryFdReadError::Offline { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceMoveEntryErrorSourceNotFound").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryFdReadErrorOffline").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceMoveEntryError::Stopped { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceMoveEntryErrorStopped").or_throw(cx)?; + libparsec::WorkspaceHistoryFdReadError::Stopped { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceHistoryFdReadErrorStopped").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } } Ok(js_obj) } -// WorkspaceOpenFileError +// WorkspaceHistoryFdStatError #[allow(dead_code)] -fn variant_workspace_open_file_error_rs_to_js<'a>( +fn variant_workspace_history_fd_stat_error_rs_to_js<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceOpenFileError, + rs_obj: libparsec::WorkspaceHistoryFdStatError, ) -> NeonResult> { let js_obj = cx.empty_object(); let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; js_obj.set(cx, "error", js_display)?; match rs_obj { - libparsec::WorkspaceOpenFileError::EntryExistsInCreateNewMode { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceOpenFileErrorEntryExistsInCreateNewMode") + libparsec::WorkspaceHistoryFdStatError::BadFileDescriptor { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryFdStatErrorBadFileDescriptor") .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceOpenFileError::EntryNotAFile { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceOpenFileErrorEntryNotAFile").or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; - } - libparsec::WorkspaceOpenFileError::EntryNotFound { .. } => { + libparsec::WorkspaceHistoryFdStatError::Internal { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceOpenFileErrorEntryNotFound").or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; - } - libparsec::WorkspaceOpenFileError::Internal { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceOpenFileErrorInternal").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryFdStatErrorInternal").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceOpenFileError::InvalidCertificate { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceOpenFileErrorInvalidCertificate").or_throw(cx)?; + } + Ok(js_obj) +} + +// WorkspaceHistoryGetWorkspaceManifestV1TimestampError + +#[allow(dead_code)] +fn variant_workspace_history_get_workspace_manifest_v1_timestamp_error_rs_to_js<'a>( + cx: &mut impl Context<'a>, + rs_obj: libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError, +) -> NeonResult> { + let js_obj = cx.empty_object(); + let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; + js_obj.set(cx, "error", js_display)?; + match rs_obj { + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Internal { .. } => { + let js_tag = JsString::try_new( + cx, + "WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInternal", + ) + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceOpenFileError::InvalidKeysBundle { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceOpenFileErrorInvalidKeysBundle").or_throw(cx)?; + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::InvalidCertificate { + .. + } => { + let js_tag = JsString::try_new( + cx, + "WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidCertificate", + ) + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceOpenFileError::InvalidManifest { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceOpenFileErrorInvalidManifest").or_throw(cx)?; + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::InvalidKeysBundle { + .. + } => { + let js_tag = JsString::try_new( + cx, + "WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidKeysBundle", + ) + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceOpenFileError::NoRealmAccess { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceOpenFileErrorNoRealmAccess").or_throw(cx)?; + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::InvalidManifest { + .. + } => { + let js_tag = JsString::try_new( + cx, + "WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidManifest", + ) + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceOpenFileError::Offline { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceOpenFileErrorOffline").or_throw(cx)?; + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::NoRealmAccess { + .. + } => { + let js_tag = JsString::try_new( + cx, + "WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorNoRealmAccess", + ) + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceOpenFileError::ReadOnlyRealm { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceOpenFileErrorReadOnlyRealm").or_throw(cx)?; + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Offline { .. } => { + let js_tag = JsString::try_new( + cx, + "WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorOffline", + ) + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceOpenFileError::Stopped { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceOpenFileErrorStopped").or_throw(cx)?; + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Stopped { .. } => { + let js_tag = JsString::try_new( + cx, + "WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorStopped", + ) + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } } Ok(js_obj) } -// WorkspaceRemoveEntryError +// WorkspaceHistoryOpenFileError #[allow(dead_code)] -fn variant_workspace_remove_entry_error_rs_to_js<'a>( +fn variant_workspace_history_open_file_error_rs_to_js<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceRemoveEntryError, + rs_obj: libparsec::WorkspaceHistoryOpenFileError, ) -> NeonResult> { let js_obj = cx.empty_object(); let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; js_obj.set(cx, "error", js_display)?; match rs_obj { - libparsec::WorkspaceRemoveEntryError::CannotRemoveRoot { .. } => { + libparsec::WorkspaceHistoryOpenFileError::EntryNotAFile { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceRemoveEntryErrorCannotRemoveRoot").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryOpenFileErrorEntryNotAFile").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceRemoveEntryError::EntryIsFile { .. } => { + libparsec::WorkspaceHistoryOpenFileError::EntryNotFound { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceRemoveEntryErrorEntryIsFile").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryOpenFileErrorEntryNotFound").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceRemoveEntryError::EntryIsFolder { .. } => { + libparsec::WorkspaceHistoryOpenFileError::Internal { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceRemoveEntryErrorEntryIsFolder").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryOpenFileErrorInternal").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceRemoveEntryError::EntryIsNonEmptyFolder { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorEntryIsNonEmptyFolder") + libparsec::WorkspaceHistoryOpenFileError::InvalidCertificate { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryOpenFileErrorInvalidCertificate") .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceRemoveEntryError::EntryNotFound { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceRemoveEntryErrorEntryNotFound").or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; - } - libparsec::WorkspaceRemoveEntryError::Internal { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorInternal").or_throw(cx)?; + libparsec::WorkspaceHistoryOpenFileError::InvalidKeysBundle { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryOpenFileErrorInvalidKeysBundle") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceRemoveEntryError::InvalidCertificate { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorInvalidCertificate") + libparsec::WorkspaceHistoryOpenFileError::InvalidManifest { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryOpenFileErrorInvalidManifest") .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceRemoveEntryError::InvalidKeysBundle { .. } => { + libparsec::WorkspaceHistoryOpenFileError::NoRealmAccess { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceRemoveEntryErrorInvalidKeysBundle").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryOpenFileErrorNoRealmAccess").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceRemoveEntryError::InvalidManifest { .. } => { + libparsec::WorkspaceHistoryOpenFileError::Offline { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceRemoveEntryErrorInvalidManifest").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryOpenFileErrorOffline").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceRemoveEntryError::NoRealmAccess { .. } => { + libparsec::WorkspaceHistoryOpenFileError::Stopped { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceRemoveEntryErrorNoRealmAccess").or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; - } - libparsec::WorkspaceRemoveEntryError::Offline { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorOffline").or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; - } - libparsec::WorkspaceRemoveEntryError::ReadOnlyRealm { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceRemoveEntryErrorReadOnlyRealm").or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; - } - libparsec::WorkspaceRemoveEntryError::Stopped { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorStopped").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryOpenFileErrorStopped").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } } Ok(js_obj) } -// WorkspaceStatEntryError +// WorkspaceHistoryStatEntryError #[allow(dead_code)] -fn variant_workspace_stat_entry_error_rs_to_js<'a>( +fn variant_workspace_history_stat_entry_error_rs_to_js<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceStatEntryError, + rs_obj: libparsec::WorkspaceHistoryStatEntryError, ) -> NeonResult> { let js_obj = cx.empty_object(); let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; js_obj.set(cx, "error", js_display)?; match rs_obj { - libparsec::WorkspaceStatEntryError::EntryNotFound { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceStatEntryErrorEntryNotFound").or_throw(cx)?; + libparsec::WorkspaceHistoryStatEntryError::EntryNotFound { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryStatEntryErrorEntryNotFound") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatEntryError::Internal { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStatEntryErrorInternal").or_throw(cx)?; + libparsec::WorkspaceHistoryStatEntryError::Internal { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceHistoryStatEntryErrorInternal").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatEntryError::InvalidCertificate { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceStatEntryErrorInvalidCertificate").or_throw(cx)?; + libparsec::WorkspaceHistoryStatEntryError::InvalidCertificate { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryStatEntryErrorInvalidCertificate") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatEntryError::InvalidKeysBundle { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceStatEntryErrorInvalidKeysBundle").or_throw(cx)?; + libparsec::WorkspaceHistoryStatEntryError::InvalidKeysBundle { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryStatEntryErrorInvalidKeysBundle") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatEntryError::InvalidManifest { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceStatEntryErrorInvalidManifest").or_throw(cx)?; + libparsec::WorkspaceHistoryStatEntryError::InvalidManifest { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryStatEntryErrorInvalidManifest") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatEntryError::NoRealmAccess { .. } => { - let js_tag = - JsString::try_new(cx, "WorkspaceStatEntryErrorNoRealmAccess").or_throw(cx)?; + libparsec::WorkspaceHistoryStatEntryError::NoRealmAccess { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryStatEntryErrorNoRealmAccess") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatEntryError::Offline { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStatEntryErrorOffline").or_throw(cx)?; + libparsec::WorkspaceHistoryStatEntryError::Offline { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceHistoryStatEntryErrorOffline").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatEntryError::Stopped { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStatEntryErrorStopped").or_throw(cx)?; + libparsec::WorkspaceHistoryStatEntryError::Stopped { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceHistoryStatEntryErrorStopped").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } } Ok(js_obj) } -// WorkspaceStatFolderChildrenError +// WorkspaceHistoryStatFolderChildrenError #[allow(dead_code)] -fn variant_workspace_stat_folder_children_error_rs_to_js<'a>( +fn variant_workspace_history_stat_folder_children_error_rs_to_js<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceStatFolderChildrenError, + rs_obj: libparsec::WorkspaceHistoryStatFolderChildrenError, ) -> NeonResult> { let js_obj = cx.empty_object(); let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; js_obj.set(cx, "error", js_display)?; match rs_obj { - libparsec::WorkspaceStatFolderChildrenError::EntryIsFile { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorEntryIsFile") - .or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; - } - libparsec::WorkspaceStatFolderChildrenError::EntryNotFound { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorEntryNotFound") - .or_throw(cx)?; - js_obj.set(cx, "tag", js_tag)?; - } - libparsec::WorkspaceStatFolderChildrenError::Internal { .. } => { + libparsec::WorkspaceHistoryStatFolderChildrenError::EntryIsFile { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorInternal").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryStatFolderChildrenErrorEntryIsFile") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatFolderChildrenError::InvalidCertificate { .. } => { + libparsec::WorkspaceHistoryStatFolderChildrenError::EntryNotFound { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorInvalidCertificate") + JsString::try_new(cx, "WorkspaceHistoryStatFolderChildrenErrorEntryNotFound") .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatFolderChildrenError::InvalidKeysBundle { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorInvalidKeysBundle") + libparsec::WorkspaceHistoryStatFolderChildrenError::Internal { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryStatFolderChildrenErrorInternal") .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatFolderChildrenError::InvalidManifest { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorInvalidManifest") - .or_throw(cx)?; + libparsec::WorkspaceHistoryStatFolderChildrenError::InvalidCertificate { .. } => { + let js_tag = JsString::try_new( + cx, + "WorkspaceHistoryStatFolderChildrenErrorInvalidCertificate", + ) + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatFolderChildrenError::NoRealmAccess { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorNoRealmAccess") - .or_throw(cx)?; + libparsec::WorkspaceHistoryStatFolderChildrenError::InvalidKeysBundle { .. } => { + let js_tag = JsString::try_new( + cx, + "WorkspaceHistoryStatFolderChildrenErrorInvalidKeysBundle", + ) + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatFolderChildrenError::Offline { .. } => { + libparsec::WorkspaceHistoryStatFolderChildrenError::InvalidManifest { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorOffline").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryStatFolderChildrenErrorInvalidManifest") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceStatFolderChildrenError::Stopped { .. } => { + libparsec::WorkspaceHistoryStatFolderChildrenError::NoRealmAccess { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorStopped").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceHistoryStatFolderChildrenErrorNoRealmAccess") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::Offline { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryStatFolderChildrenErrorOffline") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::Stopped { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceHistoryStatFolderChildrenErrorStopped") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } } Ok(js_obj) } -// WorkspaceStopError +// WorkspaceInfoError #[allow(dead_code)] -fn variant_workspace_stop_error_rs_to_js<'a>( +fn variant_workspace_info_error_rs_to_js<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceStopError, + rs_obj: libparsec::WorkspaceInfoError, ) -> NeonResult> { let js_obj = cx.empty_object(); let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; js_obj.set(cx, "error", js_display)?; match rs_obj { - libparsec::WorkspaceStopError::Internal { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStopErrorInternal").or_throw(cx)?; + libparsec::WorkspaceInfoError::Internal { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceInfoErrorInternal").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } } Ok(js_obj) } -// WorkspaceStorageCacheSize +// WorkspaceMountError #[allow(dead_code)] -fn variant_workspace_storage_cache_size_js_to_rs<'a>( +fn variant_workspace_mount_error_rs_to_js<'a>( cx: &mut impl Context<'a>, - obj: Handle<'a, JsObject>, -) -> NeonResult { - let tag = obj.get::(cx, "tag")?.value(cx); - match tag.as_str() { - "WorkspaceStorageCacheSizeCustom" => { - let size = { - let js_val: Handle = obj.get(cx, "size")?; - { - let v = js_val.value(cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } - }; - Ok(libparsec::WorkspaceStorageCacheSize::Custom { size }) + rs_obj: libparsec::WorkspaceMountError, +) -> NeonResult> { + let js_obj = cx.empty_object(); + let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; + js_obj.set(cx, "error", js_display)?; + match rs_obj { + libparsec::WorkspaceMountError::Disabled { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceMountErrorDisabled").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMountError::Internal { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceMountErrorInternal").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; } - "WorkspaceStorageCacheSizeDefault" => Ok(libparsec::WorkspaceStorageCacheSize::Default {}), - _ => cx.throw_type_error("Object is not a WorkspaceStorageCacheSize"), } + Ok(js_obj) } +// WorkspaceMoveEntryError + #[allow(dead_code)] -fn variant_workspace_storage_cache_size_rs_to_js<'a>( +fn variant_workspace_move_entry_error_rs_to_js<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceStorageCacheSize, + rs_obj: libparsec::WorkspaceMoveEntryError, ) -> NeonResult> { let js_obj = cx.empty_object(); + let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; + js_obj.set(cx, "error", js_display)?; match rs_obj { - libparsec::WorkspaceStorageCacheSize::Custom { size, .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStorageCacheSizeCustom").or_throw(cx)?; + libparsec::WorkspaceMoveEntryError::CannotMoveRoot { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceMoveEntryErrorCannotMoveRoot").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; - let js_size = JsNumber::new(cx, size as f64); - js_obj.set(cx, "size", js_size)?; } - libparsec::WorkspaceStorageCacheSize::Default { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceStorageCacheSizeDefault").or_throw(cx)?; + libparsec::WorkspaceMoveEntryError::DestinationExists { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceMoveEntryErrorDestinationExists").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::DestinationNotFound { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceMoveEntryErrorDestinationNotFound").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::Internal { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceMoveEntryErrorInternal").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::InvalidCertificate { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceMoveEntryErrorInvalidCertificate").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::InvalidKeysBundle { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceMoveEntryErrorInvalidKeysBundle").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::InvalidManifest { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceMoveEntryErrorInvalidManifest").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::NoRealmAccess { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceMoveEntryErrorNoRealmAccess").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::Offline { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceMoveEntryErrorOffline").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::ReadOnlyRealm { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceMoveEntryErrorReadOnlyRealm").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::SourceNotFound { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceMoveEntryErrorSourceNotFound").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceMoveEntryError::Stopped { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceMoveEntryErrorStopped").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } } Ok(js_obj) } -// WorkspaceWatchError +// WorkspaceOpenFileError #[allow(dead_code)] -fn variant_workspace_watch_error_rs_to_js<'a>( +fn variant_workspace_open_file_error_rs_to_js<'a>( cx: &mut impl Context<'a>, - rs_obj: libparsec::WorkspaceWatchError, + rs_obj: libparsec::WorkspaceOpenFileError, ) -> NeonResult> { let js_obj = cx.empty_object(); let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; js_obj.set(cx, "error", js_display)?; match rs_obj { - libparsec::WorkspaceWatchError::EntryNotFound { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorEntryNotFound").or_throw(cx)?; + libparsec::WorkspaceOpenFileError::EntryExistsInCreateNewMode { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceOpenFileErrorEntryExistsInCreateNewMode") + .or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceWatchError::Internal { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorInternal").or_throw(cx)?; + libparsec::WorkspaceOpenFileError::EntryNotAFile { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceOpenFileErrorEntryNotAFile").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceWatchError::InvalidCertificate { .. } => { + libparsec::WorkspaceOpenFileError::EntryNotFound { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceWatchErrorInvalidCertificate").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceOpenFileErrorEntryNotFound").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceWatchError::InvalidKeysBundle { .. } => { + libparsec::WorkspaceOpenFileError::Internal { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceOpenFileErrorInternal").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceOpenFileError::InvalidCertificate { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceWatchErrorInvalidKeysBundle").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceOpenFileErrorInvalidCertificate").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceWatchError::InvalidManifest { .. } => { + libparsec::WorkspaceOpenFileError::InvalidKeysBundle { .. } => { let js_tag = - JsString::try_new(cx, "WorkspaceWatchErrorInvalidManifest").or_throw(cx)?; + JsString::try_new(cx, "WorkspaceOpenFileErrorInvalidKeysBundle").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceWatchError::NoRealmAccess { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorNoRealmAccess").or_throw(cx)?; + libparsec::WorkspaceOpenFileError::InvalidManifest { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceOpenFileErrorInvalidManifest").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceWatchError::Offline { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorOffline").or_throw(cx)?; + libparsec::WorkspaceOpenFileError::NoRealmAccess { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceOpenFileErrorNoRealmAccess").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - libparsec::WorkspaceWatchError::Stopped { .. } => { - let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorStopped").or_throw(cx)?; + libparsec::WorkspaceOpenFileError::Offline { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceOpenFileErrorOffline").or_throw(cx)?; js_obj.set(cx, "tag", js_tag)?; } - } - Ok(js_obj) -} - -// archive_device -fn archive_device(mut cx: FunctionContext) -> JsResult { + libparsec::WorkspaceOpenFileError::ReadOnlyRealm { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceOpenFileErrorReadOnlyRealm").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceOpenFileError::Stopped { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceOpenFileErrorStopped").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + } + Ok(js_obj) +} + +// WorkspaceRemoveEntryError + +#[allow(dead_code)] +fn variant_workspace_remove_entry_error_rs_to_js<'a>( + cx: &mut impl Context<'a>, + rs_obj: libparsec::WorkspaceRemoveEntryError, +) -> NeonResult> { + let js_obj = cx.empty_object(); + let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; + js_obj.set(cx, "error", js_display)?; + match rs_obj { + libparsec::WorkspaceRemoveEntryError::CannotRemoveRoot { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceRemoveEntryErrorCannotRemoveRoot").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::EntryIsFile { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceRemoveEntryErrorEntryIsFile").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::EntryIsFolder { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceRemoveEntryErrorEntryIsFolder").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::EntryIsNonEmptyFolder { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorEntryIsNonEmptyFolder") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::EntryNotFound { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceRemoveEntryErrorEntryNotFound").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::Internal { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorInternal").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::InvalidCertificate { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorInvalidCertificate") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::InvalidKeysBundle { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceRemoveEntryErrorInvalidKeysBundle").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::InvalidManifest { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceRemoveEntryErrorInvalidManifest").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::NoRealmAccess { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceRemoveEntryErrorNoRealmAccess").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::Offline { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorOffline").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::ReadOnlyRealm { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceRemoveEntryErrorReadOnlyRealm").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceRemoveEntryError::Stopped { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceRemoveEntryErrorStopped").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + } + Ok(js_obj) +} + +// WorkspaceStatEntryError + +#[allow(dead_code)] +fn variant_workspace_stat_entry_error_rs_to_js<'a>( + cx: &mut impl Context<'a>, + rs_obj: libparsec::WorkspaceStatEntryError, +) -> NeonResult> { + let js_obj = cx.empty_object(); + let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; + js_obj.set(cx, "error", js_display)?; + match rs_obj { + libparsec::WorkspaceStatEntryError::EntryNotFound { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceStatEntryErrorEntryNotFound").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatEntryError::Internal { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStatEntryErrorInternal").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatEntryError::InvalidCertificate { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceStatEntryErrorInvalidCertificate").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatEntryError::InvalidKeysBundle { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceStatEntryErrorInvalidKeysBundle").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatEntryError::InvalidManifest { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceStatEntryErrorInvalidManifest").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatEntryError::NoRealmAccess { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceStatEntryErrorNoRealmAccess").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatEntryError::Offline { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStatEntryErrorOffline").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatEntryError::Stopped { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStatEntryErrorStopped").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + } + Ok(js_obj) +} + +// WorkspaceStatFolderChildrenError + +#[allow(dead_code)] +fn variant_workspace_stat_folder_children_error_rs_to_js<'a>( + cx: &mut impl Context<'a>, + rs_obj: libparsec::WorkspaceStatFolderChildrenError, +) -> NeonResult> { + let js_obj = cx.empty_object(); + let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; + js_obj.set(cx, "error", js_display)?; + match rs_obj { + libparsec::WorkspaceStatFolderChildrenError::EntryIsFile { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorEntryIsFile") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatFolderChildrenError::EntryNotFound { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorEntryNotFound") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatFolderChildrenError::Internal { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorInternal").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatFolderChildrenError::InvalidCertificate { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorInvalidCertificate") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatFolderChildrenError::InvalidKeysBundle { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorInvalidKeysBundle") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatFolderChildrenError::InvalidManifest { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorInvalidManifest") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatFolderChildrenError::NoRealmAccess { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorNoRealmAccess") + .or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatFolderChildrenError::Offline { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorOffline").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceStatFolderChildrenError::Stopped { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceStatFolderChildrenErrorStopped").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + } + Ok(js_obj) +} + +// WorkspaceStopError + +#[allow(dead_code)] +fn variant_workspace_stop_error_rs_to_js<'a>( + cx: &mut impl Context<'a>, + rs_obj: libparsec::WorkspaceStopError, +) -> NeonResult> { + let js_obj = cx.empty_object(); + let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; + js_obj.set(cx, "error", js_display)?; + match rs_obj { + libparsec::WorkspaceStopError::Internal { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStopErrorInternal").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + } + Ok(js_obj) +} + +// WorkspaceStorageCacheSize + +#[allow(dead_code)] +fn variant_workspace_storage_cache_size_js_to_rs<'a>( + cx: &mut impl Context<'a>, + obj: Handle<'a, JsObject>, +) -> NeonResult { + let tag = obj.get::(cx, "tag")?.value(cx); + match tag.as_str() { + "WorkspaceStorageCacheSizeCustom" => { + let size = { + let js_val: Handle = obj.get(cx, "size")?; + { + let v = js_val.value(cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + Ok(libparsec::WorkspaceStorageCacheSize::Custom { size }) + } + "WorkspaceStorageCacheSizeDefault" => Ok(libparsec::WorkspaceStorageCacheSize::Default {}), + _ => cx.throw_type_error("Object is not a WorkspaceStorageCacheSize"), + } +} + +#[allow(dead_code)] +fn variant_workspace_storage_cache_size_rs_to_js<'a>( + cx: &mut impl Context<'a>, + rs_obj: libparsec::WorkspaceStorageCacheSize, +) -> NeonResult> { + let js_obj = cx.empty_object(); + match rs_obj { + libparsec::WorkspaceStorageCacheSize::Custom { size, .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStorageCacheSizeCustom").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + let js_size = JsNumber::new(cx, size as f64); + js_obj.set(cx, "size", js_size)?; + } + libparsec::WorkspaceStorageCacheSize::Default { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceStorageCacheSizeDefault").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + } + Ok(js_obj) +} + +// WorkspaceWatchError + +#[allow(dead_code)] +fn variant_workspace_watch_error_rs_to_js<'a>( + cx: &mut impl Context<'a>, + rs_obj: libparsec::WorkspaceWatchError, +) -> NeonResult> { + let js_obj = cx.empty_object(); + let js_display = JsString::try_new(cx, &rs_obj.to_string()).or_throw(cx)?; + js_obj.set(cx, "error", js_display)?; + match rs_obj { + libparsec::WorkspaceWatchError::EntryNotFound { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorEntryNotFound").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceWatchError::Internal { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorInternal").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceWatchError::InvalidCertificate { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceWatchErrorInvalidCertificate").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceWatchError::InvalidKeysBundle { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceWatchErrorInvalidKeysBundle").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceWatchError::InvalidManifest { .. } => { + let js_tag = + JsString::try_new(cx, "WorkspaceWatchErrorInvalidManifest").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceWatchError::NoRealmAccess { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorNoRealmAccess").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceWatchError::Offline { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorOffline").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + libparsec::WorkspaceWatchError::Stopped { .. } => { + let js_tag = JsString::try_new(cx, "WorkspaceWatchErrorStopped").or_throw(cx)?; + js_obj.set(cx, "tag", js_tag)?; + } + } + Ok(js_obj) +} + +// archive_device +fn archive_device(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let device_path = { + let js_val = cx.argument::(0)?; + { + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = libparsec::archive_device(&device_path).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_archive_device_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// bootstrap_organization +fn bootstrap_organization(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let config = { + let js_val = cx.argument::(0)?; + struct_client_config_js_to_rs(&mut cx, js_val)? + }; + let on_event_callback = { + let js_val = cx.argument::(1)?; + // The Javascript function object is going to be shared between the closure + // called by rust (that can be called multiple times) and the single-use + // closure sent to the js runtime. + // So we must use an Arc to ensure the resource is shared correctly, but + // that's not all of it ! + // When the resource is no longer use, we must consume the reference we + // had on the javascript function in a neon context so that it can itself + // notify the js runtime's garbage collector. + struct Callback { + js_fn: Option>, + channel: neon::event::Channel, + } + impl Drop for Callback { + fn drop(&mut self) { + if let Some(js_fn) = self.js_fn.take() { + // Return the js object to the js runtime to avoid memory leak + self.channel.send(move |mut cx| { + js_fn.to_inner(&mut cx); + Ok(()) + }); + } + } + } + let callback = std::sync::Arc::new(Callback { + js_fn: Some(js_val.root(&mut cx)), + channel: cx.channel(), + }); + std::sync::Arc::new( + move |handle: libparsec::Handle, event: libparsec::ClientEvent| { + let callback2 = callback.clone(); + callback.channel.send(move |mut cx| { + // TODO: log an error instead of panic ? (it is a bit harsh to crash + // the current task if an unrelated event handler has a bug...) + let js_event = variant_client_event_rs_to_js(&mut cx, event)?; + let js_handle = JsNumber::new(&mut cx, handle); + if let Some(ref js_fn) = callback2.js_fn { + js_fn + .to_inner(&mut cx) + .call_with(&cx) + .args((js_handle, js_event)) + .apply::(&mut cx)?; + } + Ok(()) + }); + }, + ) as std::sync::Arc + }; + let bootstrap_organization_addr = { + let js_val = cx.argument::(2)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::ParsecOrganizationBootstrapAddr::from_any(&s).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let save_strategy = { + let js_val = cx.argument::(3)?; + variant_device_save_strategy_js_to_rs(&mut cx, js_val)? + }; + let human_handle = { + let js_val = cx.argument::(4)?; + struct_human_handle_js_to_rs(&mut cx, js_val)? + }; + let device_label = { + let js_val = cx.argument::(5)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let sequester_authority_verify_key = match cx.argument_opt(6) { + Some(v) => { + match v.downcast::, _>(&mut cx) { + Ok(js_val) => { + Some({ + #[allow(clippy::unnecessary_mut_passed)] + match js_val.as_slice(&mut cx).try_into() { + Ok(val) => val, + // err can't infer type in some case, because of the previous `try_into` + #[allow(clippy::useless_format)] + Err(err) => return cx.throw_type_error(format!("{}", err)), + } + }) + } + Err(_) => None, + } + } + None => None, + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = libparsec::bootstrap_organization( + config, + on_event_callback, + bootstrap_organization_addr, + save_strategy, + human_handle, + device_label, + sequester_authority_verify_key, + ) + .await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = struct_available_device_rs_to_js(&mut cx, ok)?; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_bootstrap_organization_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// build_parsec_organization_bootstrap_addr +fn build_parsec_organization_bootstrap_addr(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let addr = { + let js_val = cx.argument::(0)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::ParsecAddr::from_any(&s).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let organization_id = { + let js_val = cx.argument::(1)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::OrganizationID::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let ret = libparsec::build_parsec_organization_bootstrap_addr(addr, organization_id); + let js_ret = JsString::try_new(&mut cx, { + let custom_to_rs_string = + |addr: libparsec::ParsecOrganizationBootstrapAddr| -> Result { + Ok(addr.to_url().into()) + }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// cancel +fn cancel(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let canceller = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let ret = libparsec::cancel(canceller); + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_cancel_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// claimer_device_finalize_save_local_device +fn claimer_device_finalize_save_local_device(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let handle = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let save_strategy = { + let js_val = cx.argument::(1)?; + variant_device_save_strategy_js_to_rs(&mut cx, js_val)? + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = + libparsec::claimer_device_finalize_save_local_device(handle, save_strategy).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = struct_available_device_rs_to_js(&mut cx, ok)?; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// claimer_device_in_progress_1_do_deny_trust +fn claimer_device_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let canceller = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let handle = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = + libparsec::claimer_device_in_progress_1_do_deny_trust(canceller, handle).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// claimer_device_in_progress_1_do_signify_trust +fn claimer_device_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let canceller = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let handle = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = + libparsec::claimer_device_in_progress_1_do_signify_trust(canceller, handle).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = struct_device_claim_in_progress2_info_rs_to_js(&mut cx, ok)?; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// claimer_device_in_progress_2_do_wait_peer_trust +fn claimer_device_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let canceller = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let handle = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = + libparsec::claimer_device_in_progress_2_do_wait_peer_trust(canceller, handle).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = struct_device_claim_in_progress3_info_rs_to_js(&mut cx, ok)?; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// claimer_device_in_progress_3_do_claim +fn claimer_device_in_progress_3_do_claim(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let canceller = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let handle = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let requested_device_label = { + let js_val = cx.argument::(2)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = libparsec::claimer_device_in_progress_3_do_claim( + canceller, + handle, + requested_device_label, + ) + .await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = struct_device_claim_finalize_info_rs_to_js(&mut cx, ok)?; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// claimer_device_initial_do_wait_peer +fn claimer_device_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let canceller = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let handle = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = libparsec::claimer_device_initial_do_wait_peer(canceller, handle).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = struct_device_claim_in_progress1_info_rs_to_js(&mut cx, ok)?; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// claimer_greeter_abort_operation +fn claimer_greeter_abort_operation(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let device_path = { - let js_val = cx.argument::(0)?; + let handle = { + let js_val = cx.argument::(0)?; { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? } + let v = v as u32; + v } }; let channel = cx.channel(); @@ -6969,7 +8429,7 @@ fn archive_device(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::archive_device(&device_path).await; + let ret = libparsec::claimer_greeter_abort_operation(handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -6989,7 +8449,8 @@ fn archive_device(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_archive_device_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_claimer_greeter_abort_operation_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -7001,8 +8462,8 @@ fn archive_device(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// bootstrap_organization -fn bootstrap_organization(mut cx: FunctionContext) -> JsResult { +// claimer_retrieve_info +fn claimer_retrieve_info(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let config = { let js_val = cx.argument::(0)?; @@ -7057,31 +8518,11 @@ fn bootstrap_organization(mut cx: FunctionContext) -> JsResult { }, ) as std::sync::Arc }; - let bootstrap_organization_addr = { + let addr = { let js_val = cx.argument::(2)?; { let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::ParsecOrganizationBootstrapAddr::from_any(&s).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let save_strategy = { - let js_val = cx.argument::(3)?; - variant_device_save_strategy_js_to_rs(&mut cx, js_val)? - }; - let human_handle = { - let js_val = cx.argument::(4)?; - struct_human_handle_js_to_rs(&mut cx, js_val)? - }; - let device_label = { - let js_val = cx.argument::(5)?; - { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + libparsec::ParsecInvitationAddr::from_any(&s).map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -7089,25 +8530,6 @@ fn bootstrap_organization(mut cx: FunctionContext) -> JsResult { } } }; - let sequester_authority_verify_key = match cx.argument_opt(6) { - Some(v) => { - match v.downcast::, _>(&mut cx) { - Ok(js_val) => { - Some({ - #[allow(clippy::unnecessary_mut_passed)] - match js_val.as_slice(&mut cx).try_into() { - Ok(val) => val, - // err can't infer type in some case, because of the previous `try_into` - #[allow(clippy::useless_format)] - Err(err) => return cx.throw_type_error(format!("{}", err)), - } - }) - } - Err(_) => None, - } - } - None => None, - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -7116,16 +8538,7 @@ fn bootstrap_organization(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::bootstrap_organization( - config, - on_event_callback, - bootstrap_organization_addr, - save_strategy, - human_handle, - device_label, - sequester_authority_verify_key, - ) - .await; + let ret = libparsec::claimer_retrieve_info(config, on_event_callback, addr).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -7133,7 +8546,8 @@ fn bootstrap_organization(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_available_device_rs_to_js(&mut cx, ok)?; + let js_value = + variant_user_or_device_claim_initial_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -7141,7 +8555,7 @@ fn bootstrap_organization(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_bootstrap_organization_error_rs_to_js(&mut cx, err)?; + let js_err = variant_claimer_retrieve_info_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -7153,52 +8567,63 @@ fn bootstrap_organization(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// build_parsec_organization_bootstrap_addr -fn build_parsec_organization_bootstrap_addr(mut cx: FunctionContext) -> JsResult { +// claimer_user_finalize_save_local_device +fn claimer_user_finalize_save_local_device(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let addr = { - let js_val = cx.argument::(0)?; + let handle = { + let js_val = cx.argument::(0)?; { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::ParsecAddr::from_any(&s).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? } + let v = v as u32; + v } }; - let organization_id = { - let js_val = cx.argument::(1)?; - { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::OrganizationID::try_from(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } + let save_strategy = { + let js_val = cx.argument::(1)?; + variant_device_save_strategy_js_to_rs(&mut cx, js_val)? }; - let ret = libparsec::build_parsec_organization_bootstrap_addr(addr, organization_id); - let js_ret = JsString::try_new(&mut cx, { - let custom_to_rs_string = - |addr: libparsec::ParsecOrganizationBootstrapAddr| -> Result { - Ok(addr.to_url().into()) - }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; + let channel = cx.channel(); let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = + libparsec::claimer_user_finalize_save_local_device(handle, save_strategy).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = struct_available_device_rs_to_js(&mut cx, ok)?; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + Ok(promise) } -// cancel -fn cancel(mut cx: FunctionContext) -> JsResult { +// claimer_user_in_progress_1_do_deny_trust +fn claimer_user_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let canceller = { let js_val = cx.argument::(0)?; @@ -7211,39 +8636,8 @@ fn cancel(mut cx: FunctionContext) -> JsResult { v } }; - let ret = libparsec::cancel(canceller); - let js_ret = match ret { - Ok(ok) => { - let js_obj = JsObject::new(&mut cx); - let js_tag = JsBoolean::new(&mut cx, true); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) - }; - js_obj.set(&mut cx, "value", js_value)?; - js_obj - } - Err(err) => { - let js_obj = cx.empty_object(); - let js_tag = JsBoolean::new(&mut cx, false); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_cancel_error_rs_to_js(&mut cx, err)?; - js_obj.set(&mut cx, "error", js_err)?; - js_obj - } - }; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// claimer_device_finalize_save_local_device -fn claimer_device_finalize_save_local_device(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); let handle = { - let js_val = cx.argument::(0)?; + let js_val = cx.argument::(1)?; { let v = js_val.value(&mut cx); if v < (u32::MIN as f64) || (u32::MAX as f64) < v { @@ -7253,10 +8647,6 @@ fn claimer_device_finalize_save_local_device(mut cx: FunctionContext) -> JsResul v } }; - let save_strategy = { - let js_val = cx.argument::(1)?; - variant_device_save_strategy_js_to_rs(&mut cx, js_val)? - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -7265,8 +8655,7 @@ fn claimer_device_finalize_save_local_device(mut cx: FunctionContext) -> JsResul .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::claimer_device_finalize_save_local_device(handle, save_strategy).await; + let ret = libparsec::claimer_user_in_progress_1_do_deny_trust(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -7274,7 +8663,11 @@ fn claimer_device_finalize_save_local_device(mut cx: FunctionContext) -> JsResul let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_available_device_rs_to_js(&mut cx, ok)?; + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -7294,8 +8687,8 @@ fn claimer_device_finalize_save_local_device(mut cx: FunctionContext) -> JsResul Ok(promise) } -// claimer_device_in_progress_1_do_deny_trust -fn claimer_device_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResult { +// claimer_user_in_progress_1_do_signify_trust +fn claimer_user_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let canceller = { let js_val = cx.argument::(0)?; @@ -7328,7 +8721,7 @@ fn claimer_device_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResu .expect("Mutex is poisoned") .spawn(async move { let ret = - libparsec::claimer_device_in_progress_1_do_deny_trust(canceller, handle).await; + libparsec::claimer_user_in_progress_1_do_signify_trust(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -7336,11 +8729,7 @@ fn claimer_device_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResu let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) - }; + let js_value = struct_user_claim_in_progress2_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -7360,8 +8749,8 @@ fn claimer_device_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResu Ok(promise) } -// claimer_device_in_progress_1_do_signify_trust -fn claimer_device_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsResult { +// claimer_user_in_progress_2_do_wait_peer_trust +fn claimer_user_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let canceller = { let js_val = cx.argument::(0)?; @@ -7394,7 +8783,7 @@ fn claimer_device_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsR .expect("Mutex is poisoned") .spawn(async move { let ret = - libparsec::claimer_device_in_progress_1_do_signify_trust(canceller, handle).await; + libparsec::claimer_user_in_progress_2_do_wait_peer_trust(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -7402,7 +8791,7 @@ fn claimer_device_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsR let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_device_claim_in_progress2_info_rs_to_js(&mut cx, ok)?; + let js_value = struct_user_claim_in_progress3_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -7422,8 +8811,8 @@ fn claimer_device_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsR Ok(promise) } -// claimer_device_in_progress_2_do_wait_peer_trust -fn claimer_device_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> JsResult { +// claimer_user_in_progress_3_do_claim +fn claimer_user_in_progress_3_do_claim(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let canceller = { let js_val = cx.argument::(0)?; @@ -7447,6 +8836,22 @@ fn claimer_device_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> J v } }; + let requested_device_label = { + let js_val = cx.argument::(2)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let requested_human_handle = { + let js_val = cx.argument::(3)?; + struct_human_handle_js_to_rs(&mut cx, js_val)? + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -7455,8 +8860,13 @@ fn claimer_device_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> J .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::claimer_device_in_progress_2_do_wait_peer_trust(canceller, handle).await; + let ret = libparsec::claimer_user_in_progress_3_do_claim( + canceller, + handle, + requested_device_label, + requested_human_handle, + ) + .await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -7464,7 +8874,7 @@ fn claimer_device_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> J let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_device_claim_in_progress3_info_rs_to_js(&mut cx, ok)?; + let js_value = struct_user_claim_finalize_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -7484,8 +8894,8 @@ fn claimer_device_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> J Ok(promise) } -// claimer_device_in_progress_3_do_claim -fn claimer_device_in_progress_3_do_claim(mut cx: FunctionContext) -> JsResult { +// claimer_user_initial_do_wait_peer +fn claimer_user_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let canceller = { let js_val = cx.argument::(0)?; @@ -7509,18 +8919,6 @@ fn claimer_device_in_progress_3_do_claim(mut cx: FunctionContext) -> JsResult(2)?; - { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -7529,12 +8927,7 @@ fn claimer_device_in_progress_3_do_claim(mut cx: FunctionContext) -> JsResult JsResult JsResult JsResult { +// client_accept_tos +fn client_accept_tos(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { + let client = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -7576,15 +8969,18 @@ fn claimer_device_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult(1)?; { let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; let channel = cx.channel(); @@ -7595,7 +8991,7 @@ fn claimer_device_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult JsResult JsResult JsResult JsResult { +// client_cancel_invitation +fn client_cancel_invitation(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let handle = { + let client = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -7637,6 +9037,18 @@ fn claimer_greeter_abort_operation(mut cx: FunctionContext) -> JsResult(1)?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -7645,7 +9057,7 @@ fn claimer_greeter_abort_operation(mut cx: FunctionContext) -> JsResult JsResult JsResult JsResult { +// client_change_authentication +fn client_change_authentication(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let config = { + let client_config = { let js_val = cx.argument::(0)?; struct_client_config_js_to_rs(&mut cx, js_val)? }; - let on_event_callback = { - let js_val = cx.argument::(1)?; - // The Javascript function object is going to be shared between the closure - // called by rust (that can be called multiple times) and the single-use - // closure sent to the js runtime. - // So we must use an Arc to ensure the resource is shared correctly, but - // that's not all of it ! - // When the resource is no longer use, we must consume the reference we - // had on the javascript function in a neon context so that it can itself - // notify the js runtime's garbage collector. - struct Callback { - js_fn: Option>, - channel: neon::event::Channel, - } - impl Drop for Callback { - fn drop(&mut self) { - if let Some(js_fn) = self.js_fn.take() { - // Return the js object to the js runtime to avoid memory leak - self.channel.send(move |mut cx| { - js_fn.to_inner(&mut cx); - Ok(()) - }); - } - } - } - let callback = std::sync::Arc::new(Callback { - js_fn: Some(js_val.root(&mut cx)), - channel: cx.channel(), - }); - std::sync::Arc::new( - move |handle: libparsec::Handle, event: libparsec::ClientEvent| { - let callback2 = callback.clone(); - callback.channel.send(move |mut cx| { - // TODO: log an error instead of panic ? (it is a bit harsh to crash - // the current task if an unrelated event handler has a bug...) - let js_event = variant_client_event_rs_to_js(&mut cx, event)?; - let js_handle = JsNumber::new(&mut cx, handle); - if let Some(ref js_fn) = callback2.js_fn { - js_fn - .to_inner(&mut cx) - .call_with(&cx) - .args((js_handle, js_event)) - .apply::(&mut cx)?; - } - Ok(()) - }); - }, - ) as std::sync::Arc + let current_auth = { + let js_val = cx.argument::(1)?; + variant_device_access_strategy_js_to_rs(&mut cx, js_val)? }; - let addr = { - let js_val = cx.argument::(2)?; - { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::ParsecInvitationAddr::from_any(&s).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } + let new_auth = { + let js_val = cx.argument::(2)?; + variant_device_save_strategy_js_to_rs(&mut cx, js_val)? }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -7754,7 +9112,9 @@ fn claimer_retrieve_info(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::claimer_retrieve_info(config, on_event_callback, addr).await; + let ret = + libparsec::client_change_authentication(client_config, current_auth, new_auth) + .await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -7762,8 +9122,11 @@ fn claimer_retrieve_info(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = - variant_user_or_device_claim_initial_info_rs_to_js(&mut cx, ok)?; + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -7771,7 +9134,8 @@ fn claimer_retrieve_info(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_claimer_retrieve_info_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_client_change_authentication_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -7783,10 +9147,10 @@ fn claimer_retrieve_info(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// claimer_user_finalize_save_local_device -fn claimer_user_finalize_save_local_device(mut cx: FunctionContext) -> JsResult { +// client_create_workspace +fn client_create_workspace(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let handle = { + let client = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -7797,9 +9161,17 @@ fn claimer_user_finalize_save_local_device(mut cx: FunctionContext) -> JsResult< v } }; - let save_strategy = { - let js_val = cx.argument::(1)?; - variant_device_save_strategy_js_to_rs(&mut cx, js_val)? + let name = { + let js_val = cx.argument::(1)?; + { + let custom_from_rs_string = |s: String| -> Result<_, _> { + s.parse::().map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -7809,8 +9181,7 @@ fn claimer_user_finalize_save_local_device(mut cx: FunctionContext) -> JsResult< .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::claimer_user_finalize_save_local_device(handle, save_strategy).await; + let ret = libparsec::client_create_workspace(client, name).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -7818,7 +9189,17 @@ fn claimer_user_finalize_save_local_device(mut cx: FunctionContext) -> JsResult< let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_available_device_rs_to_js(&mut cx, ok)?; + let js_value = JsString::try_new(&mut cx, { + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { + Ok(x.hex()) + }; + match custom_to_rs_string(ok) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -7826,7 +9207,7 @@ fn claimer_user_finalize_save_local_device(mut cx: FunctionContext) -> JsResult< let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_create_workspace_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -7838,10 +9219,10 @@ fn claimer_user_finalize_save_local_device(mut cx: FunctionContext) -> JsResult< Ok(promise) } -// claimer_user_in_progress_1_do_deny_trust -fn claimer_user_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResult { +// client_get_tos +fn client_get_tos(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { + let client = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -7852,17 +9233,6 @@ fn claimer_user_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResult v } }; - let handle = { - let js_val = cx.argument::(1)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -7871,7 +9241,7 @@ fn claimer_user_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResult .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::claimer_user_in_progress_1_do_deny_trust(canceller, handle).await; + let ret = libparsec::client_get_tos(client).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -7879,11 +9249,7 @@ fn claimer_user_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResult let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) - }; + let js_value = struct_tos_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -7891,7 +9257,7 @@ fn claimer_user_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResult let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_get_tos_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -7903,10 +9269,10 @@ fn claimer_user_in_progress_1_do_deny_trust(mut cx: FunctionContext) -> JsResult Ok(promise) } -// claimer_user_in_progress_1_do_signify_trust -fn claimer_user_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsResult { +// client_get_user_device +fn client_get_user_device(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { + let client = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -7917,15 +9283,16 @@ fn claimer_user_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsRes v } }; - let handle = { - let js_val = cx.argument::(1)?; + let device = { + let js_val = cx.argument::(1)?; { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_string = |s: String| -> Result { + libparsec::DeviceID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; let channel = cx.channel(); @@ -7936,8 +9303,7 @@ fn claimer_user_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsRes .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::claimer_user_in_progress_1_do_signify_trust(canceller, handle).await; + let ret = libparsec::client_get_user_device(client, device).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -7945,7 +9311,15 @@ fn claimer_user_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsRes let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_user_claim_in_progress2_info_rs_to_js(&mut cx, ok)?; + let js_value = { + let (x0, x1) = ok; + let js_array = JsArray::new(&mut cx, 2); + let js_value = struct_user_info_rs_to_js(&mut cx, x0)?; + js_array.set(&mut cx, 0, js_value)?; + let js_value = struct_device_info_rs_to_js(&mut cx, x1)?; + js_array.set(&mut cx, 1, js_value)?; + js_array + }; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -7953,7 +9327,7 @@ fn claimer_user_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsRes let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_get_user_device_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -7965,10 +9339,10 @@ fn claimer_user_in_progress_1_do_signify_trust(mut cx: FunctionContext) -> JsRes Ok(promise) } -// claimer_user_in_progress_2_do_wait_peer_trust -fn claimer_user_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> JsResult { +// client_info +fn client_info(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { + let client = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -7979,17 +9353,6 @@ fn claimer_user_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> JsR v } }; - let handle = { - let js_val = cx.argument::(1)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -7998,8 +9361,7 @@ fn claimer_user_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> JsR .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::claimer_user_in_progress_2_do_wait_peer_trust(canceller, handle).await; + let ret = libparsec::client_info(client).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8007,7 +9369,7 @@ fn claimer_user_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> JsR let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_user_claim_in_progress3_info_rs_to_js(&mut cx, ok)?; + let js_value = struct_client_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -8015,7 +9377,7 @@ fn claimer_user_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> JsR let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_claim_in_progress_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_info_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8027,10 +9389,10 @@ fn claimer_user_in_progress_2_do_wait_peer_trust(mut cx: FunctionContext) -> JsR Ok(promise) } -// claimer_user_in_progress_3_do_claim -fn claimer_user_in_progress_3_do_claim(mut cx: FunctionContext) -> JsResult { +// client_list_invitations +fn client_list_invitations(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { + let client = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -8041,33 +9403,6 @@ fn claimer_user_in_progress_3_do_claim(mut cx: FunctionContext) -> JsResult(1)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } - }; - let requested_device_label = { - let js_val = cx.argument::(2)?; - { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let requested_human_handle = { - let js_val = cx.argument::(3)?; - struct_human_handle_js_to_rs(&mut cx, js_val)? - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8076,13 +9411,7 @@ fn claimer_user_in_progress_3_do_claim(mut cx: FunctionContext) -> JsResult JsResult JsResult JsResult JsResult { +// client_list_user_devices +fn client_list_user_devices(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { + let client = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -8124,15 +9461,16 @@ fn claimer_user_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult(1)?; + let user = { + let js_val = cx.argument::(1)?; { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_string = |s: String| -> Result { + libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; let channel = cx.channel(); @@ -8143,7 +9481,7 @@ fn claimer_user_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult JsResult JsResult JsResult JsResult { +// client_list_users +fn client_list_users(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8185,19 +9531,9 @@ fn client_accept_tos(mut cx: FunctionContext) -> JsResult { v } }; - let tos_updated_on = { - let js_val = cx.argument::(1)?; - { - let v = js_val.value(&mut cx); - let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { - libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) - .map_err(|_| "Out-of-bound datetime") - }; - match custom_from_rs_f64(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } + let skip_revoked = { + let js_val = cx.argument::(1)?; + js_val.value(&mut cx) }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8207,7 +9543,7 @@ fn client_accept_tos(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_accept_tos(client, tos_updated_on).await; + let ret = libparsec::client_list_users(client, skip_revoked).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8216,9 +9552,13 @@ fn client_accept_tos(mut cx: FunctionContext) -> JsResult { let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) + // JsArray::new allocates with `undefined` value, that's why we `set` value + let js_array = JsArray::new(&mut cx, ok.len()); + for (i, elem) in ok.into_iter().enumerate() { + let js_elem = struct_user_info_rs_to_js(&mut cx, elem)?; + js_array.set(&mut cx, i as u32, js_elem)?; + } + js_array }; js_obj.set(&mut cx, "value", js_value)?; js_obj @@ -8227,7 +9567,7 @@ fn client_accept_tos(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_accept_tos_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_list_users_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8239,8 +9579,8 @@ fn client_accept_tos(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_cancel_invitation -fn client_cancel_invitation(mut cx: FunctionContext) -> JsResult { +// client_list_workspace_users +fn client_list_workspace_users(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8253,11 +9593,11 @@ fn client_cancel_invitation(mut cx: FunctionContext) -> JsResult { v } }; - let token = { + let realm_id = { let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -8273,7 +9613,7 @@ fn client_cancel_invitation(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_cancel_invitation(client, token).await; + let ret = libparsec::client_list_workspace_users(client, realm_id).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8282,9 +9622,14 @@ fn client_cancel_invitation(mut cx: FunctionContext) -> JsResult { let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) + // JsArray::new allocates with `undefined` value, that's why we `set` value + let js_array = JsArray::new(&mut cx, ok.len()); + for (i, elem) in ok.into_iter().enumerate() { + let js_elem = + struct_workspace_user_access_info_rs_to_js(&mut cx, elem)?; + js_array.set(&mut cx, i as u32, js_elem)?; + } + js_array }; js_obj.set(&mut cx, "value", js_value)?; js_obj @@ -8293,7 +9638,8 @@ fn client_cancel_invitation(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_cancel_invitation_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_client_list_workspace_users_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8305,20 +9651,19 @@ fn client_cancel_invitation(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_change_authentication -fn client_change_authentication(mut cx: FunctionContext) -> JsResult { +// client_list_workspaces +fn client_list_workspaces(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let client_config = { - let js_val = cx.argument::(0)?; - struct_client_config_js_to_rs(&mut cx, js_val)? - }; - let current_auth = { - let js_val = cx.argument::(1)?; - variant_device_access_strategy_js_to_rs(&mut cx, js_val)? - }; - let new_auth = { - let js_val = cx.argument::(2)?; - variant_device_save_strategy_js_to_rs(&mut cx, js_val)? + let client = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8328,9 +9673,7 @@ fn client_change_authentication(mut cx: FunctionContext) -> JsResult .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::client_change_authentication(client_config, current_auth, new_auth) - .await; + let ret = libparsec::client_list_workspaces(client).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8339,9 +9682,13 @@ fn client_change_authentication(mut cx: FunctionContext) -> JsResult let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) + // JsArray::new allocates with `undefined` value, that's why we `set` value + let js_array = JsArray::new(&mut cx, ok.len()); + for (i, elem) in ok.into_iter().enumerate() { + let js_elem = struct_workspace_info_rs_to_js(&mut cx, elem)?; + js_array.set(&mut cx, i as u32, js_elem)?; + } + js_array }; js_obj.set(&mut cx, "value", js_value)?; js_obj @@ -8350,8 +9697,7 @@ fn client_change_authentication(mut cx: FunctionContext) -> JsResult let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = - variant_client_change_authentication_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_list_workspaces_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8363,8 +9709,8 @@ fn client_change_authentication(mut cx: FunctionContext) -> JsResult Ok(promise) } -// client_create_workspace -fn client_create_workspace(mut cx: FunctionContext) -> JsResult { +// client_new_device_invitation +fn client_new_device_invitation(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8377,17 +9723,9 @@ fn client_create_workspace(mut cx: FunctionContext) -> JsResult { v } }; - let name = { - let js_val = cx.argument::(1)?; - { - let custom_from_rs_string = |s: String| -> Result<_, _> { - s.parse::().map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } + let send_email = { + let js_val = cx.argument::(1)?; + js_val.value(&mut cx) }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8397,7 +9735,7 @@ fn client_create_workspace(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_create_workspace(client, name).await; + let ret = libparsec::client_new_device_invitation(client, send_email).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8405,17 +9743,7 @@ fn client_create_workspace(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsString::try_new(&mut cx, { - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { - Ok(x.hex()) - }; - match custom_to_rs_string(ok) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; + let js_value = struct_new_invitation_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -8423,7 +9751,8 @@ fn client_create_workspace(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_create_workspace_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_client_new_device_invitation_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8435,8 +9764,8 @@ fn client_create_workspace(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_get_tos -fn client_get_tos(mut cx: FunctionContext) -> JsResult { +// client_new_user_invitation +fn client_new_user_invitation(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8449,6 +9778,14 @@ fn client_get_tos(mut cx: FunctionContext) -> JsResult { v } }; + let claimer_email = { + let js_val = cx.argument::(1)?; + js_val.value(&mut cx) + }; + let send_email = { + let js_val = cx.argument::(2)?; + js_val.value(&mut cx) + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8457,7 +9794,8 @@ fn client_get_tos(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_get_tos(client).await; + let ret = + libparsec::client_new_user_invitation(client, claimer_email, send_email).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8465,7 +9803,7 @@ fn client_get_tos(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_tos_rs_to_js(&mut cx, ok)?; + let js_value = struct_new_invitation_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -8473,7 +9811,8 @@ fn client_get_tos(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_get_tos_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_client_new_user_invitation_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8485,8 +9824,8 @@ fn client_get_tos(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_get_user_device -fn client_get_user_device(mut cx: FunctionContext) -> JsResult { +// client_rename_workspace +fn client_rename_workspace(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8499,11 +9838,23 @@ fn client_get_user_device(mut cx: FunctionContext) -> JsResult { v } }; - let device = { + let realm_id = { let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::DeviceID::from_hex(s.as_str()).map_err(|e| e.to_string()) + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let new_name = { + let js_val = cx.argument::(2)?; + { + let custom_from_rs_string = |s: String| -> Result<_, _> { + s.parse::().map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -8519,7 +9870,7 @@ fn client_get_user_device(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_get_user_device(client, device).await; + let ret = libparsec::client_rename_workspace(client, realm_id, new_name).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8528,13 +9879,9 @@ fn client_get_user_device(mut cx: FunctionContext) -> JsResult { let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; let js_value = { - let (x0, x1) = ok; - let js_array = JsArray::new(&mut cx, 2); - let js_value = struct_user_info_rs_to_js(&mut cx, x0)?; - js_array.set(&mut cx, 0, js_value)?; - let js_value = struct_device_info_rs_to_js(&mut cx, x1)?; - js_array.set(&mut cx, 1, js_value)?; - js_array + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) }; js_obj.set(&mut cx, "value", js_value)?; js_obj @@ -8543,7 +9890,7 @@ fn client_get_user_device(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_get_user_device_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_rename_workspace_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8555,8 +9902,8 @@ fn client_get_user_device(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_info -fn client_info(mut cx: FunctionContext) -> JsResult { +// client_revoke_user +fn client_revoke_user(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8569,6 +9916,18 @@ fn client_info(mut cx: FunctionContext) -> JsResult { v } }; + let user = { + let js_val = cx.argument::(1)?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8577,7 +9936,7 @@ fn client_info(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_info(client).await; + let ret = libparsec::client_revoke_user(client, user).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8585,7 +9944,11 @@ fn client_info(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_client_info_rs_to_js(&mut cx, ok)?; + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -8593,7 +9956,7 @@ fn client_info(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_info_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_revoke_user_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8605,8 +9968,8 @@ fn client_info(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_list_invitations -fn client_list_invitations(mut cx: FunctionContext) -> JsResult { +// client_share_workspace +fn client_share_workspace(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8619,6 +9982,40 @@ fn client_list_invitations(mut cx: FunctionContext) -> JsResult { v } }; + let realm_id = { + let js_val = cx.argument::(1)?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let recipient = { + let js_val = cx.argument::(2)?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let role = match cx.argument_opt(3) { + Some(v) => match v.downcast::(&mut cx) { + Ok(js_val) => Some({ + let js_string = js_val.value(&mut cx); + enum_realm_role_js_to_rs(&mut cx, js_string.as_str())? + }), + Err(_) => None, + }, + None => None, + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8627,7 +10024,7 @@ fn client_list_invitations(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_list_invitations(client).await; + let ret = libparsec::client_share_workspace(client, realm_id, recipient, role).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8636,13 +10033,9 @@ fn client_list_invitations(mut cx: FunctionContext) -> JsResult { let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; let js_value = { - // JsArray::new allocates with `undefined` value, that's why we `set` value - let js_array = JsArray::new(&mut cx, ok.len()); - for (i, elem) in ok.into_iter().enumerate() { - let js_elem = variant_invite_list_item_rs_to_js(&mut cx, elem)?; - js_array.set(&mut cx, i as u32, js_elem)?; - } - js_array + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) }; js_obj.set(&mut cx, "value", js_value)?; js_obj @@ -8651,7 +10044,7 @@ fn client_list_invitations(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_list_invitations_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_share_workspace_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8663,31 +10056,65 @@ fn client_list_invitations(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_list_user_devices -fn client_list_user_devices(mut cx: FunctionContext) -> JsResult { +// client_start +fn client_start(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let client = { - let js_val = cx.argument::(0)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } + let config = { + let js_val = cx.argument::(0)?; + struct_client_config_js_to_rs(&mut cx, js_val)? }; - let user = { - let js_val = cx.argument::(1)?; - { - let custom_from_rs_string = |s: String| -> Result { - libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), + let on_event_callback = { + let js_val = cx.argument::(1)?; + // The Javascript function object is going to be shared between the closure + // called by rust (that can be called multiple times) and the single-use + // closure sent to the js runtime. + // So we must use an Arc to ensure the resource is shared correctly, but + // that's not all of it ! + // When the resource is no longer use, we must consume the reference we + // had on the javascript function in a neon context so that it can itself + // notify the js runtime's garbage collector. + struct Callback { + js_fn: Option>, + channel: neon::event::Channel, + } + impl Drop for Callback { + fn drop(&mut self) { + if let Some(js_fn) = self.js_fn.take() { + // Return the js object to the js runtime to avoid memory leak + self.channel.send(move |mut cx| { + js_fn.to_inner(&mut cx); + Ok(()) + }); + } } } + let callback = std::sync::Arc::new(Callback { + js_fn: Some(js_val.root(&mut cx)), + channel: cx.channel(), + }); + std::sync::Arc::new( + move |handle: libparsec::Handle, event: libparsec::ClientEvent| { + let callback2 = callback.clone(); + callback.channel.send(move |mut cx| { + // TODO: log an error instead of panic ? (it is a bit harsh to crash + // the current task if an unrelated event handler has a bug...) + let js_event = variant_client_event_rs_to_js(&mut cx, event)?; + let js_handle = JsNumber::new(&mut cx, handle); + if let Some(ref js_fn) = callback2.js_fn { + js_fn + .to_inner(&mut cx) + .call_with(&cx) + .args((js_handle, js_event)) + .apply::(&mut cx)?; + } + Ok(()) + }); + }, + ) as std::sync::Arc + }; + let access = { + let js_val = cx.argument::(2)?; + variant_device_access_strategy_js_to_rs(&mut cx, js_val)? }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8697,7 +10124,7 @@ fn client_list_user_devices(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_list_user_devices(client, user).await; + let ret = libparsec::client_start(config, on_event_callback, access).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8705,15 +10132,7 @@ fn client_list_user_devices(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - // JsArray::new allocates with `undefined` value, that's why we `set` value - let js_array = JsArray::new(&mut cx, ok.len()); - for (i, elem) in ok.into_iter().enumerate() { - let js_elem = struct_device_info_rs_to_js(&mut cx, elem)?; - js_array.set(&mut cx, i as u32, js_elem)?; - } - js_array - }; + let js_value = JsNumber::new(&mut cx, ok as f64); js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -8721,7 +10140,7 @@ fn client_list_user_devices(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_list_user_devices_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_start_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8733,8 +10152,8 @@ fn client_list_user_devices(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_list_users -fn client_list_users(mut cx: FunctionContext) -> JsResult { +// client_start_device_invitation_greet +fn client_start_device_invitation_greet(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8747,9 +10166,17 @@ fn client_list_users(mut cx: FunctionContext) -> JsResult { v } }; - let skip_revoked = { - let js_val = cx.argument::(1)?; - js_val.value(&mut cx) + let token = { + let js_val = cx.argument::(1)?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8759,7 +10186,7 @@ fn client_list_users(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_list_users(client, skip_revoked).await; + let ret = libparsec::client_start_device_invitation_greet(client, token).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8767,15 +10194,7 @@ fn client_list_users(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - // JsArray::new allocates with `undefined` value, that's why we `set` value - let js_array = JsArray::new(&mut cx, ok.len()); - for (i, elem) in ok.into_iter().enumerate() { - let js_elem = struct_user_info_rs_to_js(&mut cx, elem)?; - js_array.set(&mut cx, i as u32, js_elem)?; - } - js_array - }; + let js_value = struct_device_greet_initial_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -8783,7 +10202,8 @@ fn client_list_users(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_list_users_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_client_start_invitation_greet_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8795,8 +10215,8 @@ fn client_list_users(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_list_workspace_users -fn client_list_workspace_users(mut cx: FunctionContext) -> JsResult { +// client_start_user_invitation_greet +fn client_start_user_invitation_greet(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8809,11 +10229,11 @@ fn client_list_workspace_users(mut cx: FunctionContext) -> JsResult { v } }; - let realm_id = { + let token = { let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + let custom_from_rs_string = |s: String| -> Result { + libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -8829,7 +10249,7 @@ fn client_list_workspace_users(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_list_workspace_users(client, realm_id).await; + let ret = libparsec::client_start_user_invitation_greet(client, token).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8837,16 +10257,7 @@ fn client_list_workspace_users(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - // JsArray::new allocates with `undefined` value, that's why we `set` value - let js_array = JsArray::new(&mut cx, ok.len()); - for (i, elem) in ok.into_iter().enumerate() { - let js_elem = - struct_workspace_user_access_info_rs_to_js(&mut cx, elem)?; - js_array.set(&mut cx, i as u32, js_elem)?; - } - js_array - }; + let js_value = struct_user_greet_initial_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -8855,7 +10266,7 @@ fn client_list_workspace_users(mut cx: FunctionContext) -> JsResult { let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; let js_err = - variant_client_list_workspace_users_error_rs_to_js(&mut cx, err)?; + variant_client_start_invitation_greet_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8867,8 +10278,8 @@ fn client_list_workspace_users(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_list_workspaces -fn client_list_workspaces(mut cx: FunctionContext) -> JsResult { +// client_start_workspace +fn client_start_workspace(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8881,6 +10292,18 @@ fn client_list_workspaces(mut cx: FunctionContext) -> JsResult { v } }; + let realm_id = { + let js_val = cx.argument::(1)?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8889,7 +10312,7 @@ fn client_list_workspaces(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_list_workspaces(client).await; + let ret = libparsec::client_start_workspace(client, realm_id).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8897,15 +10320,7 @@ fn client_list_workspaces(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - // JsArray::new allocates with `undefined` value, that's why we `set` value - let js_array = JsArray::new(&mut cx, ok.len()); - for (i, elem) in ok.into_iter().enumerate() { - let js_elem = struct_workspace_info_rs_to_js(&mut cx, elem)?; - js_array.set(&mut cx, i as u32, js_elem)?; - } - js_array - }; + let js_value = JsNumber::new(&mut cx, ok as f64); js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -8913,7 +10328,7 @@ fn client_list_workspaces(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_list_workspaces_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_start_workspace_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8925,8 +10340,8 @@ fn client_list_workspaces(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_new_device_invitation -fn client_new_device_invitation(mut cx: FunctionContext) -> JsResult { +// client_stop +fn client_stop(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let client = { let js_val = cx.argument::(0)?; @@ -8939,10 +10354,6 @@ fn client_new_device_invitation(mut cx: FunctionContext) -> JsResult v } }; - let send_email = { - let js_val = cx.argument::(1)?; - js_val.value(&mut cx) - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -8951,7 +10362,7 @@ fn client_new_device_invitation(mut cx: FunctionContext) -> JsResult .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_new_device_invitation(client, send_email).await; + let ret = libparsec::client_stop(client).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -8959,7 +10370,11 @@ fn client_new_device_invitation(mut cx: FunctionContext) -> JsResult let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_new_invitation_info_rs_to_js(&mut cx, ok)?; + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -8967,8 +10382,7 @@ fn client_new_device_invitation(mut cx: FunctionContext) -> JsResult let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = - variant_client_new_device_invitation_error_rs_to_js(&mut cx, err)?; + let js_err = variant_client_stop_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -8980,10 +10394,10 @@ fn client_new_device_invitation(mut cx: FunctionContext) -> JsResult Ok(promise) } -// client_new_user_invitation -fn client_new_user_invitation(mut cx: FunctionContext) -> JsResult { +// fd_close +fn fd_close(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let client = { + let workspace = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -8994,13 +10408,21 @@ fn client_new_user_invitation(mut cx: FunctionContext) -> JsResult { v } }; - let claimer_email = { - let js_val = cx.argument::(1)?; - js_val.value(&mut cx) - }; - let send_email = { - let js_val = cx.argument::(2)?; - js_val.value(&mut cx) + let fd = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -9010,8 +10432,7 @@ fn client_new_user_invitation(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::client_new_user_invitation(client, claimer_email, send_email).await; + let ret = libparsec::fd_close(workspace, fd).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9019,7 +10440,11 @@ fn client_new_user_invitation(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_new_invitation_info_rs_to_js(&mut cx, ok)?; + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -9027,8 +10452,7 @@ fn client_new_user_invitation(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = - variant_client_new_user_invitation_error_rs_to_js(&mut cx, err)?; + let js_err = variant_workspace_fd_close_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9040,10 +10464,10 @@ fn client_new_user_invitation(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_rename_workspace -fn client_rename_workspace(mut cx: FunctionContext) -> JsResult { +// fd_flush +fn fd_flush(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let client = { + let workspace = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9054,25 +10478,17 @@ fn client_rename_workspace(mut cx: FunctionContext) -> JsResult { v } }; - let realm_id = { - let js_val = cx.argument::(1)?; + let fd = { + let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? } - } - }; - let new_name = { - let js_val = cx.argument::(2)?; - { - let custom_from_rs_string = |s: String| -> Result<_, _> { - s.parse::().map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), } @@ -9086,7 +10502,7 @@ fn client_rename_workspace(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_rename_workspace(client, realm_id, new_name).await; + let ret = libparsec::fd_flush(workspace, fd).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9106,7 +10522,7 @@ fn client_rename_workspace(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_rename_workspace_error_rs_to_js(&mut cx, err)?; + let js_err = variant_workspace_fd_flush_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9118,10 +10534,10 @@ fn client_rename_workspace(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_revoke_user -fn client_revoke_user(mut cx: FunctionContext) -> JsResult { +// fd_read +fn fd_read(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let client = { + let workspace = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9132,18 +10548,44 @@ fn client_revoke_user(mut cx: FunctionContext) -> JsResult { v } }; - let user = { - let js_val = cx.argument::(1)?; + let fd = { + let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), } } }; + let offset = { + let js_val = cx.argument::(2)?; + { + let v = js_val.value(&mut cx); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + cx.throw_type_error("Not an u64 number")? + } + let v = v as u64; + v + } + }; + let size = { + let js_val = cx.argument::(3)?; + { + let v = js_val.value(&mut cx); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + cx.throw_type_error("Not an u64 number")? + } + let v = v as u64; + v + } + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -9152,7 +10594,7 @@ fn client_revoke_user(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_revoke_user(client, user).await; + let ret = libparsec::fd_read(workspace, fd, offset, size).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9161,9 +10603,12 @@ fn client_revoke_user(mut cx: FunctionContext) -> JsResult { let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) + let mut js_buff = JsArrayBuffer::new(&mut cx, ok.len())?; + let js_buff_slice = js_buff.as_mut_slice(&mut cx); + for (i, c) in ok.iter().enumerate() { + js_buff_slice[i] = *c; + } + js_buff }; js_obj.set(&mut cx, "value", js_value)?; js_obj @@ -9172,7 +10617,7 @@ fn client_revoke_user(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_revoke_user_error_rs_to_js(&mut cx, err)?; + let js_err = variant_workspace_fd_read_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9184,10 +10629,10 @@ fn client_revoke_user(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_share_workspace -fn client_share_workspace(mut cx: FunctionContext) -> JsResult { +// fd_resize +fn fd_resize(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let client = { + let workspace = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9198,39 +10643,36 @@ fn client_share_workspace(mut cx: FunctionContext) -> JsResult { v } }; - let realm_id = { - let js_val = cx.argument::(1)?; + let fd = { + let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), } } }; - let recipient = { - let js_val = cx.argument::(2)?; + let length = { + let js_val = cx.argument::(2)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), + let v = js_val.value(&mut cx); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + cx.throw_type_error("Not an u64 number")? } + let v = v as u64; + v } }; - let role = match cx.argument_opt(3) { - Some(v) => match v.downcast::(&mut cx) { - Ok(js_val) => Some({ - let js_string = js_val.value(&mut cx); - enum_realm_role_js_to_rs(&mut cx, js_string.as_str())? - }), - Err(_) => None, - }, - None => None, + let truncate_only = { + let js_val = cx.argument::(3)?; + js_val.value(&mut cx) }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -9240,7 +10682,7 @@ fn client_share_workspace(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_share_workspace(client, realm_id, recipient, role).await; + let ret = libparsec::fd_resize(workspace, fd, length, truncate_only).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9260,7 +10702,7 @@ fn client_share_workspace(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_share_workspace_error_rs_to_js(&mut cx, err)?; + let js_err = variant_workspace_fd_resize_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9272,65 +10714,50 @@ fn client_share_workspace(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_start -fn client_start(mut cx: FunctionContext) -> JsResult { +// fd_write +fn fd_write(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let config = { - let js_val = cx.argument::(0)?; - struct_client_config_js_to_rs(&mut cx, js_val)? + let workspace = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } }; - let on_event_callback = { - let js_val = cx.argument::(1)?; - // The Javascript function object is going to be shared between the closure - // called by rust (that can be called multiple times) and the single-use - // closure sent to the js runtime. - // So we must use an Arc to ensure the resource is shared correctly, but - // that's not all of it ! - // When the resource is no longer use, we must consume the reference we - // had on the javascript function in a neon context so that it can itself - // notify the js runtime's garbage collector. - struct Callback { - js_fn: Option>, - channel: neon::event::Channel, + let fd = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } } - impl Drop for Callback { - fn drop(&mut self) { - if let Some(js_fn) = self.js_fn.take() { - // Return the js object to the js runtime to avoid memory leak - self.channel.send(move |mut cx| { - js_fn.to_inner(&mut cx); - Ok(()) - }); - } + }; + let offset = { + let js_val = cx.argument::(2)?; + { + let v = js_val.value(&mut cx); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + cx.throw_type_error("Not an u64 number")? } + let v = v as u64; + v } - let callback = std::sync::Arc::new(Callback { - js_fn: Some(js_val.root(&mut cx)), - channel: cx.channel(), - }); - std::sync::Arc::new( - move |handle: libparsec::Handle, event: libparsec::ClientEvent| { - let callback2 = callback.clone(); - callback.channel.send(move |mut cx| { - // TODO: log an error instead of panic ? (it is a bit harsh to crash - // the current task if an unrelated event handler has a bug...) - let js_event = variant_client_event_rs_to_js(&mut cx, event)?; - let js_handle = JsNumber::new(&mut cx, handle); - if let Some(ref js_fn) = callback2.js_fn { - js_fn - .to_inner(&mut cx) - .call_with(&cx) - .args((js_handle, js_event)) - .apply::(&mut cx)?; - } - Ok(()) - }); - }, - ) as std::sync::Arc }; - let access = { - let js_val = cx.argument::(2)?; - variant_device_access_strategy_js_to_rs(&mut cx, js_val)? + let data = { + let js_val = cx.argument::>(3)?; + js_val.as_slice(&mut cx).to_vec() }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -9340,7 +10767,7 @@ fn client_start(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_start(config, on_event_callback, access).await; + let ret = libparsec::fd_write(workspace, fd, offset, &data).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9356,7 +10783,7 @@ fn client_start(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_start_error_rs_to_js(&mut cx, err)?; + let js_err = variant_workspace_fd_write_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9368,10 +10795,10 @@ fn client_start(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// client_start_device_invitation_greet -fn client_start_device_invitation_greet(mut cx: FunctionContext) -> JsResult { +// fd_write_constrained_io +fn fd_write_constrained_io(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let client = { + let workspace = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9382,18 +10809,37 @@ fn client_start_device_invitation_greet(mut cx: FunctionContext) -> JsResult(1)?; + let fd = { + let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), } } }; + let offset = { + let js_val = cx.argument::(2)?; + { + let v = js_val.value(&mut cx); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + cx.throw_type_error("Not an u64 number")? + } + let v = v as u64; + v + } + }; + let data = { + let js_val = cx.argument::>(3)?; + js_val.as_slice(&mut cx).to_vec() + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -9402,7 +10848,7 @@ fn client_start_device_invitation_greet(mut cx: FunctionContext) -> JsResult JsResult JsResult JsResult JsResult { +// fd_write_start_eof +fn fd_write_start_eof(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let client = { + let workspace = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9445,18 +10890,26 @@ fn client_start_user_invitation_greet(mut cx: FunctionContext) -> JsResult(1)?; + let fd = { + let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), } } }; + let data = { + let js_val = cx.argument::>(2)?; + js_val.as_slice(&mut cx).to_vec() + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -9465,7 +10918,7 @@ fn client_start_user_invitation_greet(mut cx: FunctionContext) -> JsResult JsResult JsResult JsResult JsResult { +// get_default_config_dir +fn get_default_config_dir(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let ret = libparsec::get_default_config_dir(); + let js_ret = JsString::try_new(&mut cx, { + let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { + path.into_os_string() + .into_string() + .map_err(|_| "Path contains non-utf8 characters") + }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// get_default_data_base_dir +fn get_default_data_base_dir(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let ret = libparsec::get_default_data_base_dir(); + let js_ret = JsString::try_new(&mut cx, { + let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { + path.into_os_string() + .into_string() + .map_err(|_| "Path contains non-utf8 characters") + }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// get_default_mountpoint_base_dir +fn get_default_mountpoint_base_dir(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let ret = libparsec::get_default_mountpoint_base_dir(); + let js_ret = JsString::try_new(&mut cx, { + let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { + path.into_os_string() + .into_string() + .map_err(|_| "Path contains non-utf8 characters") + }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// get_platform +fn get_platform(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let ret = libparsec::get_platform(); + let js_ret = JsString::try_new(&mut cx, enum_platform_rs_to_js(ret)).or_throw(&mut cx)?; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// greeter_device_in_progress_1_do_wait_peer_trust +fn greeter_device_in_progress_1_do_wait_peer_trust(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let client = { + let canceller = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9508,59 +11033,8 @@ fn client_start_workspace(mut cx: FunctionContext) -> JsResult { v } }; - let realm_id = { - let js_val = cx.argument::(1)?; - { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - - // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? - let _handle = crate::TOKIO_RUNTIME - .lock() - .expect("Mutex is poisoned") - .spawn(async move { - let ret = libparsec::client_start_workspace(client, realm_id).await; - - deferred.settle_with(&channel, move |mut cx| { - let js_ret = match ret { - Ok(ok) => { - let js_obj = JsObject::new(&mut cx); - let js_tag = JsBoolean::new(&mut cx, true); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsNumber::new(&mut cx, ok as f64); - js_obj.set(&mut cx, "value", js_value)?; - js_obj - } - Err(err) => { - let js_obj = cx.empty_object(); - let js_tag = JsBoolean::new(&mut cx, false); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_start_workspace_error_rs_to_js(&mut cx, err)?; - js_obj.set(&mut cx, "error", js_err)?; - js_obj - } - }; - Ok(js_ret) - }); - }); - - Ok(promise) -} - -// client_stop -fn client_stop(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let client = { - let js_val = cx.argument::(0)?; + let handle = { + let js_val = cx.argument::(1)?; { let v = js_val.value(&mut cx); if v < (u32::MIN as f64) || (u32::MAX as f64) < v { @@ -9578,7 +11052,8 @@ fn client_stop(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::client_stop(client).await; + let ret = + libparsec::greeter_device_in_progress_1_do_wait_peer_trust(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9586,11 +11061,7 @@ fn client_stop(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) - }; + let js_value = struct_device_greet_in_progress2_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -9598,7 +11069,7 @@ fn client_stop(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_client_stop_error_rs_to_js(&mut cx, err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9610,10 +11081,10 @@ fn client_stop(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// fd_close -fn fd_close(mut cx: FunctionContext) -> JsResult { +// greeter_device_in_progress_2_do_deny_trust +fn greeter_device_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let workspace = { + let canceller = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9624,7 +11095,7 @@ fn fd_close(mut cx: FunctionContext) -> JsResult { v } }; - let fd = { + let handle = { let js_val = cx.argument::(1)?; { let v = js_val.value(&mut cx); @@ -9632,12 +11103,7 @@ fn fd_close(mut cx: FunctionContext) -> JsResult { cx.throw_type_error("Not an u32 number")? } let v = v as u32; - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - match custom_from_rs_u32(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } + v } }; let channel = cx.channel(); @@ -9648,7 +11114,8 @@ fn fd_close(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::fd_close(workspace, fd).await; + let ret = + libparsec::greeter_device_in_progress_2_do_deny_trust(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9668,7 +11135,7 @@ fn fd_close(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_fd_close_error_rs_to_js(&mut cx, err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9680,10 +11147,10 @@ fn fd_close(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// fd_flush -fn fd_flush(mut cx: FunctionContext) -> JsResult { +// greeter_device_in_progress_2_do_signify_trust +fn greeter_device_in_progress_2_do_signify_trust(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let workspace = { + let canceller = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9694,7 +11161,7 @@ fn fd_flush(mut cx: FunctionContext) -> JsResult { v } }; - let fd = { + let handle = { let js_val = cx.argument::(1)?; { let v = js_val.value(&mut cx); @@ -9702,12 +11169,7 @@ fn fd_flush(mut cx: FunctionContext) -> JsResult { cx.throw_type_error("Not an u32 number")? } let v = v as u32; - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - match custom_from_rs_u32(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } + v } }; let channel = cx.channel(); @@ -9718,7 +11180,8 @@ fn fd_flush(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::fd_flush(workspace, fd).await; + let ret = + libparsec::greeter_device_in_progress_2_do_signify_trust(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9726,11 +11189,7 @@ fn fd_flush(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) - }; + let js_value = struct_device_greet_in_progress3_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -9738,7 +11197,7 @@ fn fd_flush(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_fd_flush_error_rs_to_js(&mut cx, err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9750,10 +11209,12 @@ fn fd_flush(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// fd_read -fn fd_read(mut cx: FunctionContext) -> JsResult { +// greeter_device_in_progress_3_do_get_claim_requests +fn greeter_device_in_progress_3_do_get_claim_requests( + mut cx: FunctionContext, +) -> JsResult { crate::init_sentry(); - let workspace = { + let canceller = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9764,7 +11225,7 @@ fn fd_read(mut cx: FunctionContext) -> JsResult { v } }; - let fd = { + let handle = { let js_val = cx.argument::(1)?; { let v = js_val.value(&mut cx); @@ -9772,33 +11233,6 @@ fn fd_read(mut cx: FunctionContext) -> JsResult { cx.throw_type_error("Not an u32 number")? } let v = v as u32; - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - match custom_from_rs_u32(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let offset = { - let js_val = cx.argument::(2)?; - { - let v = js_val.value(&mut cx); - if v < (u64::MIN as f64) || (u64::MAX as f64) < v { - cx.throw_type_error("Not an u64 number")? - } - let v = v as u64; - v - } - }; - let size = { - let js_val = cx.argument::(3)?; - { - let v = js_val.value(&mut cx); - if v < (u64::MIN as f64) || (u64::MAX as f64) < v { - cx.throw_type_error("Not an u64 number")? - } - let v = v as u64; v } }; @@ -9810,7 +11244,9 @@ fn fd_read(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::fd_read(workspace, fd, offset, size).await; + let ret = + libparsec::greeter_device_in_progress_3_do_get_claim_requests(canceller, handle) + .await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9818,14 +11254,7 @@ fn fd_read(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - let mut js_buff = JsArrayBuffer::new(&mut cx, ok.len())?; - let js_buff_slice = js_buff.as_mut_slice(&mut cx); - for (i, c) in ok.iter().enumerate() { - js_buff_slice[i] = *c; - } - js_buff - }; + let js_value = struct_device_greet_in_progress4_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -9833,7 +11262,7 @@ fn fd_read(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_fd_read_error_rs_to_js(&mut cx, err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9845,10 +11274,10 @@ fn fd_read(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// fd_resize -fn fd_resize(mut cx: FunctionContext) -> JsResult { +// greeter_device_in_progress_4_do_create +fn greeter_device_in_progress_4_do_create(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let workspace = { + let canceller = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9859,7 +11288,7 @@ fn fd_resize(mut cx: FunctionContext) -> JsResult { v } }; - let fd = { + let handle = { let js_val = cx.argument::(1)?; { let v = js_val.value(&mut cx); @@ -9867,29 +11296,21 @@ fn fd_resize(mut cx: FunctionContext) -> JsResult { cx.throw_type_error("Not an u32 number")? } let v = v as u32; - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - match custom_from_rs_u32(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } + v } }; - let length = { - let js_val = cx.argument::(2)?; + let device_label = { + let js_val = cx.argument::(2)?; { - let v = js_val.value(&mut cx); - if v < (u64::MIN as f64) || (u64::MAX as f64) < v { - cx.throw_type_error("Not an u64 number")? + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u64; - v } }; - let truncate_only = { - let js_val = cx.argument::(3)?; - js_val.value(&mut cx) - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -9898,7 +11319,9 @@ fn fd_resize(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::fd_resize(workspace, fd, length, truncate_only).await; + let ret = + libparsec::greeter_device_in_progress_4_do_create(canceller, handle, device_label) + .await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9918,7 +11341,7 @@ fn fd_resize(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_fd_resize_error_rs_to_js(&mut cx, err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -9930,10 +11353,10 @@ fn fd_resize(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// fd_write -fn fd_write(mut cx: FunctionContext) -> JsResult { +// greeter_device_initial_do_wait_peer +fn greeter_device_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let workspace = { + let canceller = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -9944,7 +11367,7 @@ fn fd_write(mut cx: FunctionContext) -> JsResult { v } }; - let fd = { + let handle = { let js_val = cx.argument::(1)?; { let v = js_val.value(&mut cx); @@ -9952,29 +11375,9 @@ fn fd_write(mut cx: FunctionContext) -> JsResult { cx.throw_type_error("Not an u32 number")? } let v = v as u32; - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - match custom_from_rs_u32(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let offset = { - let js_val = cx.argument::(2)?; - { - let v = js_val.value(&mut cx); - if v < (u64::MIN as f64) || (u64::MAX as f64) < v { - cx.throw_type_error("Not an u64 number")? - } - let v = v as u64; v } }; - let data = { - let js_val = cx.argument::>(3)?; - js_val.as_slice(&mut cx).to_vec() - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -9983,7 +11386,7 @@ fn fd_write(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::fd_write(workspace, fd, offset, &data).await; + let ret = libparsec::greeter_device_initial_do_wait_peer(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -9991,7 +11394,7 @@ fn fd_write(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsNumber::new(&mut cx, ok as f64); + let js_value = struct_device_greet_in_progress1_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -9999,7 +11402,7 @@ fn fd_write(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_fd_write_error_rs_to_js(&mut cx, err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -10010,11 +11413,11 @@ fn fd_write(mut cx: FunctionContext) -> JsResult { Ok(promise) } - -// fd_write_constrained_io -fn fd_write_constrained_io(mut cx: FunctionContext) -> JsResult { + +// greeter_user_in_progress_1_do_wait_peer_trust +fn greeter_user_in_progress_1_do_wait_peer_trust(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let workspace = { + let canceller = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -10025,7 +11428,7 @@ fn fd_write_constrained_io(mut cx: FunctionContext) -> JsResult { v } }; - let fd = { + let handle = { let js_val = cx.argument::(1)?; { let v = js_val.value(&mut cx); @@ -10033,29 +11436,9 @@ fn fd_write_constrained_io(mut cx: FunctionContext) -> JsResult { cx.throw_type_error("Not an u32 number")? } let v = v as u32; - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - match custom_from_rs_u32(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let offset = { - let js_val = cx.argument::(2)?; - { - let v = js_val.value(&mut cx); - if v < (u64::MIN as f64) || (u64::MAX as f64) < v { - cx.throw_type_error("Not an u64 number")? - } - let v = v as u64; v } }; - let data = { - let js_val = cx.argument::>(3)?; - js_val.as_slice(&mut cx).to_vec() - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -10064,7 +11447,8 @@ fn fd_write_constrained_io(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::fd_write_constrained_io(workspace, fd, offset, &data).await; + let ret = + libparsec::greeter_user_in_progress_1_do_wait_peer_trust(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -10072,7 +11456,7 @@ fn fd_write_constrained_io(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsNumber::new(&mut cx, ok as f64); + let js_value = struct_user_greet_in_progress2_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -10080,7 +11464,7 @@ fn fd_write_constrained_io(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_fd_write_error_rs_to_js(&mut cx, err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -10092,10 +11476,10 @@ fn fd_write_constrained_io(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// fd_write_start_eof -fn fd_write_start_eof(mut cx: FunctionContext) -> JsResult { +// greeter_user_in_progress_2_do_deny_trust +fn greeter_user_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let workspace = { + let canceller = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -10106,7 +11490,7 @@ fn fd_write_start_eof(mut cx: FunctionContext) -> JsResult { v } }; - let fd = { + let handle = { let js_val = cx.argument::(1)?; { let v = js_val.value(&mut cx); @@ -10114,18 +11498,9 @@ fn fd_write_start_eof(mut cx: FunctionContext) -> JsResult { cx.throw_type_error("Not an u32 number")? } let v = v as u32; - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - match custom_from_rs_u32(v) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } + v } }; - let data = { - let js_val = cx.argument::>(2)?; - js_val.as_slice(&mut cx).to_vec() - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -10134,7 +11509,7 @@ fn fd_write_start_eof(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::fd_write_start_eof(workspace, fd, &data).await; + let ret = libparsec::greeter_user_in_progress_2_do_deny_trust(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -10142,7 +11517,11 @@ fn fd_write_start_eof(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsNumber::new(&mut cx, ok as f64); + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -10150,7 +11529,7 @@ fn fd_write_start_eof(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_fd_write_error_rs_to_js(&mut cx, err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -10162,81 +11541,8 @@ fn fd_write_start_eof(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// get_default_config_dir -fn get_default_config_dir(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let ret = libparsec::get_default_config_dir(); - let js_ret = JsString::try_new(&mut cx, { - let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { - path.into_os_string() - .into_string() - .map_err(|_| "Path contains non-utf8 characters") - }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// get_default_data_base_dir -fn get_default_data_base_dir(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let ret = libparsec::get_default_data_base_dir(); - let js_ret = JsString::try_new(&mut cx, { - let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { - path.into_os_string() - .into_string() - .map_err(|_| "Path contains non-utf8 characters") - }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// get_default_mountpoint_base_dir -fn get_default_mountpoint_base_dir(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let ret = libparsec::get_default_mountpoint_base_dir(); - let js_ret = JsString::try_new(&mut cx, { - let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { - path.into_os_string() - .into_string() - .map_err(|_| "Path contains non-utf8 characters") - }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// get_platform -fn get_platform(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let ret = libparsec::get_platform(); - let js_ret = JsString::try_new(&mut cx, enum_platform_rs_to_js(ret)).or_throw(&mut cx)?; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// greeter_device_in_progress_1_do_wait_peer_trust -fn greeter_device_in_progress_1_do_wait_peer_trust(mut cx: FunctionContext) -> JsResult { +// greeter_user_in_progress_2_do_signify_trust +fn greeter_user_in_progress_2_do_signify_trust(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let canceller = { let js_val = cx.argument::(0)?; @@ -10269,7 +11575,7 @@ fn greeter_device_in_progress_1_do_wait_peer_trust(mut cx: FunctionContext) -> J .expect("Mutex is poisoned") .spawn(async move { let ret = - libparsec::greeter_device_in_progress_1_do_wait_peer_trust(canceller, handle).await; + libparsec::greeter_user_in_progress_2_do_signify_trust(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -10277,7 +11583,7 @@ fn greeter_device_in_progress_1_do_wait_peer_trust(mut cx: FunctionContext) -> J let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_device_greet_in_progress2_info_rs_to_js(&mut cx, ok)?; + let js_value = struct_user_greet_in_progress3_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -10297,8 +11603,10 @@ fn greeter_device_in_progress_1_do_wait_peer_trust(mut cx: FunctionContext) -> J Ok(promise) } -// greeter_device_in_progress_2_do_deny_trust -fn greeter_device_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResult { +// greeter_user_in_progress_3_do_get_claim_requests +fn greeter_user_in_progress_3_do_get_claim_requests( + mut cx: FunctionContext, +) -> JsResult { crate::init_sentry(); let canceller = { let js_val = cx.argument::(0)?; @@ -10331,7 +11639,8 @@ fn greeter_device_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResu .expect("Mutex is poisoned") .spawn(async move { let ret = - libparsec::greeter_device_in_progress_2_do_deny_trust(canceller, handle).await; + libparsec::greeter_user_in_progress_3_do_get_claim_requests(canceller, handle) + .await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -10339,11 +11648,7 @@ fn greeter_device_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResu let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) - }; + let js_value = struct_user_greet_in_progress4_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -10363,8 +11668,8 @@ fn greeter_device_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResu Ok(promise) } -// greeter_device_in_progress_2_do_signify_trust -fn greeter_device_in_progress_2_do_signify_trust(mut cx: FunctionContext) -> JsResult { +// greeter_user_in_progress_4_do_create +fn greeter_user_in_progress_4_do_create(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let canceller = { let js_val = cx.argument::(0)?; @@ -10388,6 +11693,29 @@ fn greeter_device_in_progress_2_do_signify_trust(mut cx: FunctionContext) -> JsR v } }; + let human_handle = { + let js_val = cx.argument::(2)?; + struct_human_handle_js_to_rs(&mut cx, js_val)? + }; + let device_label = { + let js_val = cx.argument::(3)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let profile = { + let js_val = cx.argument::(4)?; + { + let js_string = js_val.value(&mut cx); + enum_user_profile_js_to_rs(&mut cx, js_string.as_str())? + } + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -10396,8 +11724,14 @@ fn greeter_device_in_progress_2_do_signify_trust(mut cx: FunctionContext) -> JsR .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::greeter_device_in_progress_2_do_signify_trust(canceller, handle).await; + let ret = libparsec::greeter_user_in_progress_4_do_create( + canceller, + handle, + human_handle, + device_label, + profile, + ) + .await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -10405,7 +11739,11 @@ fn greeter_device_in_progress_2_do_signify_trust(mut cx: FunctionContext) -> JsR let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_device_greet_in_progress3_info_rs_to_js(&mut cx, ok)?; + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) + }; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -10425,10 +11763,8 @@ fn greeter_device_in_progress_2_do_signify_trust(mut cx: FunctionContext) -> JsR Ok(promise) } -// greeter_device_in_progress_3_do_get_claim_requests -fn greeter_device_in_progress_3_do_get_claim_requests( - mut cx: FunctionContext, -) -> JsResult { +// greeter_user_initial_do_wait_peer +fn greeter_user_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let canceller = { let js_val = cx.argument::(0)?; @@ -10460,9 +11796,7 @@ fn greeter_device_in_progress_3_do_get_claim_requests( .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::greeter_device_in_progress_3_do_get_claim_requests(canceller, handle) - .await; + let ret = libparsec::greeter_user_initial_do_wait_peer(canceller, handle).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -10470,7 +11804,7 @@ fn greeter_device_in_progress_3_do_get_claim_requests( let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_device_greet_in_progress4_info_rs_to_js(&mut cx, ok)?; + let js_value = struct_user_greet_in_progress1_info_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -10490,10 +11824,61 @@ fn greeter_device_in_progress_3_do_get_claim_requests( Ok(promise) } -// greeter_device_in_progress_4_do_create -fn greeter_device_in_progress_4_do_create(mut cx: FunctionContext) -> JsResult { +// is_keyring_available +fn is_keyring_available(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let ret = libparsec::is_keyring_available(); + let js_ret = JsBoolean::new(&mut cx, ret); + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// list_available_devices +fn list_available_devices(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let path = { + let js_val = cx.argument::(0)?; + { + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = libparsec::list_available_devices(&path).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = { + // JsArray::new allocates with `undefined` value, that's why we `set` value + let js_array = JsArray::new(&mut cx, ret.len()); + for (i, elem) in ret.into_iter().enumerate() { + let js_elem = struct_available_device_rs_to_js(&mut cx, elem)?; + js_array.set(&mut cx, i as u32, js_elem)?; + } + js_array + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// mountpoint_to_os_path +fn mountpoint_to_os_path(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { + let mountpoint = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -10504,22 +11889,11 @@ fn greeter_device_in_progress_4_do_create(mut cx: FunctionContext) -> JsResult(1)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } - }; - let device_label = { - let js_val = cx.argument::(2)?; + let parsec_path = { + let js_val = cx.argument::(1)?; { let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + s.parse::().map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -10535,9 +11909,7 @@ fn greeter_device_in_progress_4_do_create(mut cx: FunctionContext) -> JsResult JsResult Result<_, _> { + path.into_os_string() + .into_string() + .map_err(|_| "Path contains non-utf8 characters") + }; + match custom_to_rs_string(ok) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -10557,7 +11936,7 @@ fn greeter_device_in_progress_4_do_create(mut cx: FunctionContext) -> JsResult JsResult JsResult { +// mountpoint_unmount +fn mountpoint_unmount(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { + let mountpoint = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -10583,17 +11962,6 @@ fn greeter_device_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult(1)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } - }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -10602,7 +11970,7 @@ fn greeter_device_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult JsResult JsResult JsResult JsResult { +// new_canceller +fn new_canceller(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { - let js_val = cx.argument::(0)?; + let ret = libparsec::new_canceller(); + let js_ret = JsNumber::new(&mut cx, ret as f64); + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// parse_parsec_addr +fn parse_parsec_addr(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let url = { + let js_val = cx.argument::(0)?; + js_val.value(&mut cx) + }; + let ret = libparsec::parse_parsec_addr(&url); + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = variant_parsed_parsec_addr_rs_to_js(&mut cx, ok)?; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_parse_parsec_addr_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// path_filename +fn path_filename(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let path = { + let js_val = cx.argument::(0)?; { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; - let handle = { - let js_val = cx.argument::(1)?; + let ret = libparsec::path_filename(&path); + let js_ret = match ret { + Some(elem) => JsString::try_new(&mut cx, elem) + .or_throw(&mut cx)? + .as_value(&mut cx), + None => JsNull::new(&mut cx).as_value(&mut cx), + }; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// path_join +fn path_join(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let parent = { + let js_val = cx.argument::(0)?; { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; - let channel = cx.channel(); + let child = { + let js_val = cx.argument::(1)?; + { + let custom_from_rs_string = |s: String| -> Result<_, _> { + s.parse::().map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let ret = libparsec::path_join(&parent, &child); + let js_ret = JsString::try_new(&mut cx, { + let custom_to_rs_string = + |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} - // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? - let _handle = crate::TOKIO_RUNTIME - .lock() - .expect("Mutex is poisoned") - .spawn(async move { - let ret = - libparsec::greeter_user_in_progress_1_do_wait_peer_trust(canceller, handle).await; - - deferred.settle_with(&channel, move |mut cx| { - let js_ret = match ret { - Ok(ok) => { - let js_obj = JsObject::new(&mut cx); - let js_tag = JsBoolean::new(&mut cx, true); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_user_greet_in_progress2_info_rs_to_js(&mut cx, ok)?; - js_obj.set(&mut cx, "value", js_value)?; - js_obj - } - Err(err) => { - let js_obj = cx.empty_object(); - let js_tag = JsBoolean::new(&mut cx, false); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; - js_obj.set(&mut cx, "error", js_err)?; - js_obj - } - }; - Ok(js_ret) - }); - }); +// path_normalize +fn path_normalize(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let path = { + let js_val = cx.argument::(0)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let ret = libparsec::path_normalize(path); + let js_ret = JsString::try_new(&mut cx, { + let custom_to_rs_string = + |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} +// path_parent +fn path_parent(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let path = { + let js_val = cx.argument::(0)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let ret = libparsec::path_parent(&path); + let js_ret = JsString::try_new(&mut cx, { + let custom_to_rs_string = + |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); Ok(promise) } -// greeter_user_in_progress_2_do_deny_trust -fn greeter_user_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResult { +// path_split +fn path_split(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { - let js_val = cx.argument::(0)?; + let path = { + let js_val = cx.argument::(0)?; { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; - let handle = { - let js_val = cx.argument::(1)?; + let ret = libparsec::path_split(&path); + let js_ret = { + // JsArray::new allocates with `undefined` value, that's why we `set` value + let js_array = JsArray::new(&mut cx, ret.len()); + for (i, elem) in ret.into_iter().enumerate() { + let js_elem = JsString::try_new(&mut cx, elem).or_throw(&mut cx)?; + js_array.set(&mut cx, i as u32, js_elem)?; + } + js_array + }; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// test_drop_testbed +fn test_drop_testbed(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let path = { + let js_val = cx.argument::(0)?; { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; let channel = cx.channel(); @@ -10725,7 +12224,7 @@ fn greeter_user_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResult .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::greeter_user_in_progress_2_do_deny_trust(canceller, handle).await; + let ret = libparsec::test_drop_testbed(&path).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -10745,7 +12244,7 @@ fn greeter_user_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResult let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; + let js_err = variant_testbed_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -10757,95 +12256,120 @@ fn greeter_user_in_progress_2_do_deny_trust(mut cx: FunctionContext) -> JsResult Ok(promise) } -// greeter_user_in_progress_2_do_signify_trust -fn greeter_user_in_progress_2_do_signify_trust(mut cx: FunctionContext) -> JsResult { +// test_get_testbed_bootstrap_organization_addr +fn test_get_testbed_bootstrap_organization_addr(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { - let js_val = cx.argument::(0)?; + let discriminant_dir = { + let js_val = cx.argument::(0)?; { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; - let handle = { - let js_val = cx.argument::(1)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v + let ret = libparsec::test_get_testbed_bootstrap_organization_addr(&discriminant_dir); + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = match ok { + Some(elem) => { + JsString::try_new(&mut cx,{ + let custom_to_rs_string = |addr: libparsec::ParsecOrganizationBootstrapAddr| -> Result { Ok(addr.to_url().into()) }; + match custom_to_rs_string(elem) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } +}).or_throw(&mut cx)?.as_value(&mut cx) + } + None => JsNull::new(&mut cx).as_value(&mut cx), +}; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_testbed_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj } }; - let channel = cx.channel(); let (deferred, promise) = cx.promise(); - - // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? - let _handle = crate::TOKIO_RUNTIME - .lock() - .expect("Mutex is poisoned") - .spawn(async move { - let ret = - libparsec::greeter_user_in_progress_2_do_signify_trust(canceller, handle).await; - - deferred.settle_with(&channel, move |mut cx| { - let js_ret = match ret { - Ok(ok) => { - let js_obj = JsObject::new(&mut cx); - let js_tag = JsBoolean::new(&mut cx, true); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_user_greet_in_progress3_info_rs_to_js(&mut cx, ok)?; - js_obj.set(&mut cx, "value", js_value)?; - js_obj - } - Err(err) => { - let js_obj = cx.empty_object(); - let js_tag = JsBoolean::new(&mut cx, false); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; - js_obj.set(&mut cx, "error", js_err)?; - js_obj - } - }; - Ok(js_ret) - }); - }); - + deferred.resolve(&mut cx, js_ret); Ok(promise) } -// greeter_user_in_progress_3_do_get_claim_requests -fn greeter_user_in_progress_3_do_get_claim_requests( - mut cx: FunctionContext, -) -> JsResult { +// test_get_testbed_organization_id +fn test_get_testbed_organization_id(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { - let js_val = cx.argument::(0)?; + let discriminant_dir = { + let js_val = cx.argument::(0)?; { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; - let handle = { - let js_val = cx.argument::(1)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v + let ret = libparsec::test_get_testbed_organization_id(&discriminant_dir); + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = match ok { + Some(elem) => JsString::try_new(&mut cx, elem) + .or_throw(&mut cx)? + .as_value(&mut cx), + None => JsNull::new(&mut cx).as_value(&mut cx), + }; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_testbed_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj } }; + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// test_new_testbed +fn test_new_testbed(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let template = { + let js_val = cx.argument::(0)?; + js_val.value(&mut cx) + }; + let test_server = match cx.argument_opt(1) { + Some(v) => match v.downcast::(&mut cx) { + Ok(js_val) => Some({ + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::ParsecAddr::from_any(&s).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + }), + Err(_) => None, + }, + None => None, + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -10854,9 +12378,7 @@ fn greeter_user_in_progress_3_do_get_claim_requests( .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = - libparsec::greeter_user_in_progress_3_do_get_claim_requests(canceller, handle) - .await; + let ret = libparsec::test_new_testbed(&template, test_server.as_ref()).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -10864,7 +12386,18 @@ fn greeter_user_in_progress_3_do_get_claim_requests( let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = struct_user_greet_in_progress4_info_rs_to_js(&mut cx, ok)?; + let js_value = JsString::try_new(&mut cx, { + let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { + path.into_os_string() + .into_string() + .map_err(|_| "Path contains non-utf8 characters") + }; + match custom_to_rs_string(ok) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -10872,7 +12405,7 @@ fn greeter_user_in_progress_3_do_get_claim_requests( let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_greet_in_progress_error_rs_to_js(&mut cx, err)?; + let js_err = variant_testbed_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -10884,52 +12417,128 @@ fn greeter_user_in_progress_3_do_get_claim_requests( Ok(promise) } -// greeter_user_in_progress_4_do_create -fn greeter_user_in_progress_4_do_create(mut cx: FunctionContext) -> JsResult { +// validate_device_label +fn validate_device_label(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { - let js_val = cx.argument::(0)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } + let raw = { + let js_val = cx.argument::(0)?; + js_val.value(&mut cx) }; - let handle = { - let js_val = cx.argument::(1)?; - { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? - } - let v = v as u32; - v - } + let ret = libparsec::validate_device_label(&raw); + let js_ret = JsBoolean::new(&mut cx, ret); + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// validate_email +fn validate_email(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let raw = { + let js_val = cx.argument::(0)?; + js_val.value(&mut cx) }; - let human_handle = { - let js_val = cx.argument::(2)?; - struct_human_handle_js_to_rs(&mut cx, js_val)? + let ret = libparsec::validate_email(&raw); + let js_ret = JsBoolean::new(&mut cx, ret); + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// validate_entry_name +fn validate_entry_name(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let raw = { + let js_val = cx.argument::(0)?; + js_val.value(&mut cx) }; - let device_label = { - let js_val = cx.argument::(3)?; + let ret = libparsec::validate_entry_name(&raw); + let js_ret = JsBoolean::new(&mut cx, ret); + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// validate_human_handle_label +fn validate_human_handle_label(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let raw = { + let js_val = cx.argument::(0)?; + js_val.value(&mut cx) + }; + let ret = libparsec::validate_human_handle_label(&raw); + let js_ret = JsBoolean::new(&mut cx, ret); + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// validate_invitation_token +fn validate_invitation_token(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let raw = { + let js_val = cx.argument::(0)?; + js_val.value(&mut cx) + }; + let ret = libparsec::validate_invitation_token(&raw); + let js_ret = JsBoolean::new(&mut cx, ret); + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// validate_organization_id +fn validate_organization_id(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let raw = { + let js_val = cx.argument::(0)?; + js_val.value(&mut cx) + }; + let ret = libparsec::validate_organization_id(&raw); + let js_ret = JsBoolean::new(&mut cx, ret); + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// validate_path +fn validate_path(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let raw = { + let js_val = cx.argument::(0)?; + js_val.value(&mut cx) + }; + let ret = libparsec::validate_path(&raw); + let js_ret = JsBoolean::new(&mut cx, ret); + let (deferred, promise) = cx.promise(); + deferred.resolve(&mut cx, js_ret); + Ok(promise) +} + +// wait_for_device_available +fn wait_for_device_available(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let config_dir = { + let js_val = cx.argument::(0)?; { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) - }; + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), } } }; - let profile = { - let js_val = cx.argument::(4)?; + let device_id = { + let js_val = cx.argument::(1)?; { - let js_string = js_val.value(&mut cx); - enum_user_profile_js_to_rs(&mut cx, js_string.as_str())? + let custom_from_rs_string = |s: String| -> Result { + libparsec::DeviceID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } } }; let channel = cx.channel(); @@ -10940,14 +12549,7 @@ fn greeter_user_in_progress_4_do_create(mut cx: FunctionContext) -> JsResult JsResult JsResult JsResult { +// workspace_create_file +fn workspace_create_file(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let canceller = { + let workspace = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -10993,15 +12596,16 @@ fn greeter_user_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult(1)?; + let path = { + let js_val = cx.argument::(1)?; { - let v = js_val.value(&mut cx); - if v < (u32::MIN as f64) || (u32::MAX as f64) < v { - cx.throw_type_error("Not an u32 number")? + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), } - let v = v as u32; - v } }; let channel = cx.channel(); @@ -11012,7 +12616,7 @@ fn greeter_user_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult JsResult Result { + Ok(x.hex()) + }; + match custom_to_rs_string(ok) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -11028,7 +12642,7 @@ fn greeter_user_initial_do_wait_peer(mut cx: FunctionContext) -> JsResult JsResult JsResult { - crate::init_sentry(); - let ret = libparsec::is_keyring_available(); - let js_ret = JsBoolean::new(&mut cx, ret); - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// list_available_devices -fn list_available_devices(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let path = { - let js_val = cx.argument::(0)?; - { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let channel = cx.channel(); - let (deferred, promise) = cx.promise(); - - // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? - let _handle = crate::TOKIO_RUNTIME - .lock() - .expect("Mutex is poisoned") - .spawn(async move { - let ret = libparsec::list_available_devices(&path).await; - - deferred.settle_with(&channel, move |mut cx| { - let js_ret = { - // JsArray::new allocates with `undefined` value, that's why we `set` value - let js_array = JsArray::new(&mut cx, ret.len()); - for (i, elem) in ret.into_iter().enumerate() { - let js_elem = struct_available_device_rs_to_js(&mut cx, elem)?; - js_array.set(&mut cx, i as u32, js_elem)?; - } - js_array - }; - Ok(js_ret) - }); - }); - - Ok(promise) -} - -// mountpoint_to_os_path -fn mountpoint_to_os_path(mut cx: FunctionContext) -> JsResult { +// workspace_create_folder +fn workspace_create_folder(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let mountpoint = { + let workspace = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -11105,7 +12668,7 @@ fn mountpoint_to_os_path(mut cx: FunctionContext) -> JsResult { v } }; - let parsec_path = { + let path = { let js_val = cx.argument::(1)?; { let custom_from_rs_string = |s: String| -> Result<_, String> { @@ -11125,7 +12688,7 @@ fn mountpoint_to_os_path(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::mountpoint_to_os_path(mountpoint, parsec_path).await; + let ret = libparsec::workspace_create_folder(workspace, path).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -11134,11 +12697,10 @@ fn mountpoint_to_os_path(mut cx: FunctionContext) -> JsResult { let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; let js_value = JsString::try_new(&mut cx, { - let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { - path.into_os_string() - .into_string() - .map_err(|_| "Path contains non-utf8 characters") - }; + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { + Ok(x.hex()) + }; match custom_to_rs_string(ok) { Ok(ok) => ok, Err(err) => return cx.throw_type_error(err), @@ -11152,7 +12714,7 @@ fn mountpoint_to_os_path(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_mountpoint_to_os_path_error_rs_to_js(&mut cx, err)?; + let js_err = variant_workspace_create_folder_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -11164,10 +12726,10 @@ fn mountpoint_to_os_path(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// mountpoint_unmount -fn mountpoint_unmount(mut cx: FunctionContext) -> JsResult { +// workspace_create_folder_all +fn workspace_create_folder_all(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let mountpoint = { + let workspace = { let js_val = cx.argument::(0)?; { let v = js_val.value(&mut cx); @@ -11178,6 +12740,18 @@ fn mountpoint_unmount(mut cx: FunctionContext) -> JsResult { v } }; + let path = { + let js_val = cx.argument::(1)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + match custom_from_rs_string(js_val.value(&mut cx)) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -11186,7 +12760,7 @@ fn mountpoint_unmount(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::mountpoint_unmount(mountpoint).await; + let ret = libparsec::workspace_create_folder_all(workspace, path).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -11194,11 +12768,17 @@ fn mountpoint_unmount(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) - }; + let js_value = JsString::try_new(&mut cx, { + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { + Ok(x.hex()) + }; + match custom_to_rs_string(ok) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -11206,7 +12786,7 @@ fn mountpoint_unmount(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_mountpoint_unmount_error_rs_to_js(&mut cx, err)?; + let js_err = variant_workspace_create_folder_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -11218,214 +12798,26 @@ fn mountpoint_unmount(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// new_canceller -fn new_canceller(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let ret = libparsec::new_canceller(); - let js_ret = JsNumber::new(&mut cx, ret as f64); - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// parse_parsec_addr -fn parse_parsec_addr(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let url = { - let js_val = cx.argument::(0)?; - js_val.value(&mut cx) - }; - let ret = libparsec::parse_parsec_addr(&url); - let js_ret = match ret { - Ok(ok) => { - let js_obj = JsObject::new(&mut cx); - let js_tag = JsBoolean::new(&mut cx, true); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = variant_parsed_parsec_addr_rs_to_js(&mut cx, ok)?; - js_obj.set(&mut cx, "value", js_value)?; - js_obj - } - Err(err) => { - let js_obj = cx.empty_object(); - let js_tag = JsBoolean::new(&mut cx, false); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_parse_parsec_addr_error_rs_to_js(&mut cx, err)?; - js_obj.set(&mut cx, "error", js_err)?; - js_obj - } - }; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// path_filename -fn path_filename(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let path = { - let js_val = cx.argument::(0)?; - { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let ret = libparsec::path_filename(&path); - let js_ret = match ret { - Some(elem) => JsString::try_new(&mut cx, elem) - .or_throw(&mut cx)? - .as_value(&mut cx), - None => JsNull::new(&mut cx).as_value(&mut cx), - }; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// path_join -fn path_join(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let parent = { - let js_val = cx.argument::(0)?; - { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let child = { - let js_val = cx.argument::(1)?; - { - let custom_from_rs_string = |s: String| -> Result<_, _> { - s.parse::().map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let ret = libparsec::path_join(&parent, &child); - let js_ret = JsString::try_new(&mut cx, { - let custom_to_rs_string = - |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// path_normalize -fn path_normalize(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let path = { - let js_val = cx.argument::(0)?; - { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let ret = libparsec::path_normalize(path); - let js_ret = JsString::try_new(&mut cx, { - let custom_to_rs_string = - |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// path_parent -fn path_parent(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let path = { - let js_val = cx.argument::(0)?; - { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let ret = libparsec::path_parent(&path); - let js_ret = JsString::try_new(&mut cx, { - let custom_to_rs_string = - |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// path_split -fn path_split(mut cx: FunctionContext) -> JsResult { +// workspace_decrypt_path_addr +fn workspace_decrypt_path_addr(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let path = { - let js_val = cx.argument::(0)?; + let workspace = { + let js_val = cx.argument::(0)?; { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - } - }; - let ret = libparsec::path_split(&path); - let js_ret = { - // JsArray::new allocates with `undefined` value, that's why we `set` value - let js_array = JsArray::new(&mut cx, ret.len()); - for (i, elem) in ret.into_iter().enumerate() { - let js_elem = JsString::try_new(&mut cx, elem).or_throw(&mut cx)?; - js_array.set(&mut cx, i as u32, js_elem)?; + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v } - js_array }; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// test_drop_testbed -fn test_drop_testbed(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let path = { - let js_val = cx.argument::(0)?; + let link = { + let js_val = cx.argument::(1)?; { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::ParsecWorkspacePathAddr::from_any(&s).map_err(|e| e.to_string()) + }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), @@ -11440,7 +12832,7 @@ fn test_drop_testbed(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::test_drop_testbed(&path).await; + let ret = libparsec::workspace_decrypt_path_addr(workspace, &link).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -11448,11 +12840,17 @@ fn test_drop_testbed(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) - }; + let js_value = JsString::try_new(&mut cx, { + let custom_to_rs_string = + |path: libparsec::FsPath| -> Result<_, &'static str> { + Ok(path.to_string()) + }; + match custom_to_rs_string(ok) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + }) + .or_throw(&mut cx)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -11460,7 +12858,8 @@ fn test_drop_testbed(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_testbed_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_workspace_decrypt_path_addr_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -11472,80 +12871,115 @@ fn test_drop_testbed(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// test_get_testbed_bootstrap_organization_addr -fn test_get_testbed_bootstrap_organization_addr(mut cx: FunctionContext) -> JsResult { +// workspace_generate_path_addr +fn workspace_generate_path_addr(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let discriminant_dir = { - let js_val = cx.argument::(0)?; + let workspace = { + let js_val = cx.argument::(0)?; { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let path = { + let js_val = cx.argument::(1)?; + { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), } } }; - let ret = libparsec::test_get_testbed_bootstrap_organization_addr(&discriminant_dir); - let js_ret = match ret { - Ok(ok) => { - let js_obj = JsObject::new(&mut cx); - let js_tag = JsBoolean::new(&mut cx, true); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = match ok { - Some(elem) => { - JsString::try_new(&mut cx,{ - let custom_to_rs_string = |addr: libparsec::ParsecOrganizationBootstrapAddr| -> Result { Ok(addr.to_url().into()) }; - match custom_to_rs_string(elem) { + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME.lock().expect("Mutex is poisoned").spawn(async move { + + let ret = libparsec::workspace_generate_path_addr( + workspace, + &path, + ).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = JsString::try_new(&mut cx,{ + let custom_to_rs_string = |addr: libparsec::ParsecWorkspacePathAddr| -> Result { Ok(addr.to_url().into()) }; + match custom_to_rs_string(ok) { Ok(ok) => ok, Err(err) => return cx.throw_type_error(err), } -}).or_throw(&mut cx)?.as_value(&mut cx) +}).or_throw(&mut cx)?; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_workspace_generate_path_addr_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj } - None => JsNull::new(&mut cx).as_value(&mut cx), }; - js_obj.set(&mut cx, "value", js_value)?; - js_obj - } - Err(err) => { - let js_obj = cx.empty_object(); - let js_tag = JsBoolean::new(&mut cx, false); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_testbed_error_rs_to_js(&mut cx, err)?; - js_obj.set(&mut cx, "error", js_err)?; - js_obj - } - }; - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); + Ok(js_ret) + }); + }); + Ok(promise) } -// test_get_testbed_organization_id -fn test_get_testbed_organization_id(mut cx: FunctionContext) -> JsResult { +// workspace_history_fd_close +fn workspace_history_fd_close(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let discriminant_dir = { - let js_val = cx.argument::(0)?; + let workspace = { + let js_val = cx.argument::(0)?; { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; - match custom_from_rs_string(js_val.value(&mut cx)) { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let fd = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), } } }; - let ret = libparsec::test_get_testbed_organization_id(&discriminant_dir); + let ret = libparsec::workspace_history_fd_close(workspace, fd); let js_ret = match ret { Ok(ok) => { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = match ok { - Some(elem) => JsString::try_new(&mut cx, elem) - .or_throw(&mut cx)? - .as_value(&mut cx), - None => JsNull::new(&mut cx).as_value(&mut cx), + let js_value = { + #[allow(clippy::let_unit_value)] + let _ = ok; + JsNull::new(&mut cx) }; js_obj.set(&mut cx, "value", js_value)?; js_obj @@ -11554,7 +12988,7 @@ fn test_get_testbed_organization_id(mut cx: FunctionContext) -> JsResult JsResult JsResult { +// workspace_history_fd_read +fn workspace_history_fd_read(mut cx: FunctionContext) -> JsResult { + crate::init_sentry(); + let workspace = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } + }; + let fd = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let offset = { + let js_val = cx.argument::(2)?; + { + let v = js_val.value(&mut cx); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + cx.throw_type_error("Not an u64 number")? + } + let v = v as u64; + v + } + }; + let size = { + let js_val = cx.argument::(3)?; + { + let v = js_val.value(&mut cx); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + cx.throw_type_error("Not an u64 number")? + } + let v = v as u64; + v + } + }; + let channel = cx.channel(); + let (deferred, promise) = cx.promise(); + + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = libparsec::workspace_history_fd_read(workspace, fd, offset, size).await; + + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = { + let mut js_buff = JsArrayBuffer::new(&mut cx, ok.len())?; + let js_buff_slice = js_buff.as_mut_slice(&mut cx); + for (i, c) in ok.iter().enumerate() { + js_buff_slice[i] = *c; + } + js_buff + }; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = + variant_workspace_history_fd_read_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); + }); + + Ok(promise) +} + +// workspace_history_fd_stat +fn workspace_history_fd_stat(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let template = { - let js_val = cx.argument::(0)?; - js_val.value(&mut cx) + let workspace = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } }; - let test_server = match cx.argument_opt(1) { - Some(v) => match v.downcast::(&mut cx) { - Ok(js_val) => Some({ - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::ParsecAddr::from_any(&s).map_err(|e| e.to_string()) - }; - match custom_from_rs_string(js_val.value(&mut cx)) { - Ok(val) => val, - Err(err) => return cx.throw_type_error(err), - } - }), - Err(_) => None, - }, - None => None, + let fd = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + match custom_from_rs_u32(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } }; let channel = cx.channel(); let (deferred, promise) = cx.promise(); @@ -11594,7 +13132,7 @@ fn test_new_testbed(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::test_new_testbed(&template, test_server.as_ref()).await; + let ret = libparsec::workspace_history_fd_stat(workspace, fd).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -11602,18 +13140,7 @@ fn test_new_testbed(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsString::try_new(&mut cx, { - let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { - path.into_os_string() - .into_string() - .map_err(|_| "Path contains non-utf8 characters") - }; - match custom_to_rs_string(ok) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; + let js_value = struct_workspace_history_file_stat_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -11621,7 +13148,8 @@ fn test_new_testbed(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_testbed_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_workspace_history_fd_stat_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -11633,123 +13161,102 @@ fn test_new_testbed(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// validate_device_label -fn validate_device_label(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let raw = { - let js_val = cx.argument::(0)?; - js_val.value(&mut cx) - }; - let ret = libparsec::validate_device_label(&raw); - let js_ret = JsBoolean::new(&mut cx, ret); - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// validate_email -fn validate_email(mut cx: FunctionContext) -> JsResult { +// workspace_history_get_workspace_manifest_v1_timestamp +fn workspace_history_get_workspace_manifest_v1_timestamp( + mut cx: FunctionContext, +) -> JsResult { crate::init_sentry(); - let raw = { - let js_val = cx.argument::(0)?; - js_val.value(&mut cx) + let workspace = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } }; - let ret = libparsec::validate_email(&raw); - let js_ret = JsBoolean::new(&mut cx, ret); + let channel = cx.channel(); let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} -// validate_entry_name -fn validate_entry_name(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let raw = { - let js_val = cx.argument::(0)?; - js_val.value(&mut cx) - }; - let ret = libparsec::validate_entry_name(&raw); - let js_ret = JsBoolean::new(&mut cx, ret); - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} + // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? + let _handle = crate::TOKIO_RUNTIME.lock().expect("Mutex is poisoned").spawn(async move { -// validate_human_handle_label -fn validate_human_handle_label(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let raw = { - let js_val = cx.argument::(0)?; - js_val.value(&mut cx) - }; - let ret = libparsec::validate_human_handle_label(&raw); - let js_ret = JsBoolean::new(&mut cx, ret); - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} + let ret = libparsec::workspace_history_get_workspace_manifest_v1_timestamp( + workspace, + ).await; -// validate_invitation_token -fn validate_invitation_token(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let raw = { - let js_val = cx.argument::(0)?; - js_val.value(&mut cx) - }; - let ret = libparsec::validate_invitation_token(&raw); - let js_ret = JsBoolean::new(&mut cx, ret); - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = match ok { + Some(elem) => { + JsNumber::new(&mut cx,{ + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) }; + match custom_to_rs_f64(elem) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } +}).as_value(&mut cx) + } + None => JsNull::new(&mut cx).as_value(&mut cx), +}; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_workspace_history_get_workspace_manifest_v1_timestamp_error_rs_to_js(&mut cx, err)?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } +}; + Ok(js_ret) + }); + }); -// validate_organization_id -fn validate_organization_id(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let raw = { - let js_val = cx.argument::(0)?; - js_val.value(&mut cx) - }; - let ret = libparsec::validate_organization_id(&raw); - let js_ret = JsBoolean::new(&mut cx, ret); - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); Ok(promise) } -// validate_path -fn validate_path(mut cx: FunctionContext) -> JsResult { +// workspace_history_open_file +fn workspace_history_open_file(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); - let raw = { - let js_val = cx.argument::(0)?; - js_val.value(&mut cx) + let workspace = { + let js_val = cx.argument::(0)?; + { + let v = js_val.value(&mut cx); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + cx.throw_type_error("Not an u32 number")? + } + let v = v as u32; + v + } }; - let ret = libparsec::validate_path(&raw); - let js_ret = JsBoolean::new(&mut cx, ret); - let (deferred, promise) = cx.promise(); - deferred.resolve(&mut cx, js_ret); - Ok(promise) -} - -// wait_for_device_available -fn wait_for_device_available(mut cx: FunctionContext) -> JsResult { - crate::init_sentry(); - let config_dir = { - let js_val = cx.argument::(0)?; + let at = { + let js_val = cx.argument::(1)?; { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; - match custom_from_rs_string(js_val.value(&mut cx)) { + let v = js_val.value(&mut cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { Ok(val) => val, Err(err) => return cx.throw_type_error(err), } } }; - let device_id = { - let js_val = cx.argument::(1)?; + let path = { + let js_val = cx.argument::(2)?; { - let custom_from_rs_string = |s: String| -> Result { - libparsec::DeviceID::from_hex(s.as_str()).map_err(|e| e.to_string()) + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -11765,7 +13272,7 @@ fn wait_for_device_available(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::wait_for_device_available(&config_dir, device_id).await; + let ret = libparsec::workspace_history_open_file(workspace, at, path).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -11773,11 +13280,16 @@ fn wait_for_device_available(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = { - #[allow(clippy::let_unit_value)] - let _ = ok; - JsNull::new(&mut cx) - }; + let js_value = JsNumber::new(&mut cx, { + let custom_to_rs_u32 = + |fd: libparsec::FileDescriptor| -> Result<_, &'static str> { + Ok(fd.0) + }; + match custom_to_rs_u32(ok) { + Ok(ok) => ok, + Err(err) => return cx.throw_type_error(err), + } + } as f64); js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -11786,7 +13298,7 @@ fn wait_for_device_available(mut cx: FunctionContext) -> JsResult { let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; let js_err = - variant_wait_for_device_available_error_rs_to_js(&mut cx, err)?; + variant_workspace_history_open_file_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -11798,8 +13310,8 @@ fn wait_for_device_available(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// workspace_create_file -fn workspace_create_file(mut cx: FunctionContext) -> JsResult { +// workspace_history_open_file_by_id +fn workspace_history_open_file_by_id(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let workspace = { let js_val = cx.argument::(0)?; @@ -11808,15 +13320,29 @@ fn workspace_create_file(mut cx: FunctionContext) -> JsResult { if v < (u32::MIN as f64) || (u32::MAX as f64) < v { cx.throw_type_error("Not an u32 number")? } - let v = v as u32; - v + let v = v as u32; + v + } + }; + let at = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } } }; - let path = { - let js_val = cx.argument::(1)?; + let entry_id = { + let js_val = cx.argument::(2)?; { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -11832,7 +13358,7 @@ fn workspace_create_file(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::workspace_create_file(workspace, path).await; + let ret = libparsec::workspace_history_open_file_by_id(workspace, at, entry_id).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -11840,17 +13366,16 @@ fn workspace_create_file(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsString::try_new(&mut cx, { - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { - Ok(x.hex()) + let js_value = JsNumber::new(&mut cx, { + let custom_to_rs_u32 = + |fd: libparsec::FileDescriptor| -> Result<_, &'static str> { + Ok(fd.0) }; - match custom_to_rs_string(ok) { + match custom_to_rs_u32(ok) { Ok(ok) => ok, Err(err) => return cx.throw_type_error(err), } - }) - .or_throw(&mut cx)?; + } as f64); js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -11858,7 +13383,8 @@ fn workspace_create_file(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_create_file_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_workspace_history_open_file_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -11870,8 +13396,8 @@ fn workspace_create_file(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// workspace_create_folder -fn workspace_create_folder(mut cx: FunctionContext) -> JsResult { +// workspace_history_stat_entry +fn workspace_history_stat_entry(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let workspace = { let js_val = cx.argument::(0)?; @@ -11884,8 +13410,22 @@ fn workspace_create_folder(mut cx: FunctionContext) -> JsResult { v } }; + let at = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; let path = { - let js_val = cx.argument::(1)?; + let js_val = cx.argument::(2)?; { let custom_from_rs_string = |s: String| -> Result<_, String> { s.parse::().map_err(|e| e.to_string()) @@ -11904,7 +13444,7 @@ fn workspace_create_folder(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::workspace_create_folder(workspace, path).await; + let ret = libparsec::workspace_history_stat_entry(workspace, at, &path).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -11912,17 +13452,7 @@ fn workspace_create_folder(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsString::try_new(&mut cx, { - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { - Ok(x.hex()) - }; - match custom_to_rs_string(ok) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; + let js_value = variant_workspace_history_entry_stat_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -11930,7 +13460,8 @@ fn workspace_create_folder(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_create_folder_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_workspace_history_stat_entry_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -11942,8 +13473,8 @@ fn workspace_create_folder(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// workspace_create_folder_all -fn workspace_create_folder_all(mut cx: FunctionContext) -> JsResult { +// workspace_history_stat_entry_by_id +fn workspace_history_stat_entry_by_id(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let workspace = { let js_val = cx.argument::(0)?; @@ -11956,11 +13487,25 @@ fn workspace_create_folder_all(mut cx: FunctionContext) -> JsResult { v } }; - let path = { - let js_val = cx.argument::(1)?; + let at = { + let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) + let v = js_val.value(&mut cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let entry_id = { + let js_val = cx.argument::(2)?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -11976,7 +13521,7 @@ fn workspace_create_folder_all(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::workspace_create_folder_all(workspace, path).await; + let ret = libparsec::workspace_history_stat_entry_by_id(workspace, at, entry_id).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -11984,17 +13529,7 @@ fn workspace_create_folder_all(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsString::try_new(&mut cx, { - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { - Ok(x.hex()) - }; - match custom_to_rs_string(ok) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } - }) - .or_throw(&mut cx)?; + let js_value = variant_workspace_history_entry_stat_rs_to_js(&mut cx, ok)?; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -12002,7 +13537,8 @@ fn workspace_create_folder_all(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_create_folder_error_rs_to_js(&mut cx, err)?; + let js_err = + variant_workspace_history_stat_entry_error_rs_to_js(&mut cx, err)?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -12014,8 +13550,8 @@ fn workspace_create_folder_all(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// workspace_decrypt_path_addr -fn workspace_decrypt_path_addr(mut cx: FunctionContext) -> JsResult { +// workspace_history_stat_folder_children +fn workspace_history_stat_folder_children(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let workspace = { let js_val = cx.argument::(0)?; @@ -12028,11 +13564,25 @@ fn workspace_decrypt_path_addr(mut cx: FunctionContext) -> JsResult { v } }; - let link = { - let js_val = cx.argument::(1)?; + let at = { + let js_val = cx.argument::(1)?; + { + let v = js_val.value(&mut cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let path = { + let js_val = cx.argument::(2)?; { let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::ParsecWorkspacePathAddr::from_any(&s).map_err(|e| e.to_string()) + s.parse::().map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -12048,7 +13598,7 @@ fn workspace_decrypt_path_addr(mut cx: FunctionContext) -> JsResult { .lock() .expect("Mutex is poisoned") .spawn(async move { - let ret = libparsec::workspace_decrypt_path_addr(workspace, &link).await; + let ret = libparsec::workspace_history_stat_folder_children(workspace, at, &path).await; deferred.settle_with(&channel, move |mut cx| { let js_ret = match ret { @@ -12056,17 +13606,25 @@ fn workspace_decrypt_path_addr(mut cx: FunctionContext) -> JsResult { let js_obj = JsObject::new(&mut cx); let js_tag = JsBoolean::new(&mut cx, true); js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsString::try_new(&mut cx, { - let custom_to_rs_string = - |path: libparsec::FsPath| -> Result<_, &'static str> { - Ok(path.to_string()) + let js_value = { + // JsArray::new allocates with `undefined` value, that's why we `set` value + let js_array = JsArray::new(&mut cx, ok.len()); + for (i, elem) in ok.into_iter().enumerate() { + let js_elem = { + let (x0, x1) = elem; + let js_array = JsArray::new(&mut cx, 2); + let js_value = + JsString::try_new(&mut cx, x0).or_throw(&mut cx)?; + js_array.set(&mut cx, 0, js_value)?; + let js_value = + variant_workspace_history_entry_stat_rs_to_js(&mut cx, x1)?; + js_array.set(&mut cx, 1, js_value)?; + js_array }; - match custom_to_rs_string(ok) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), + js_array.set(&mut cx, i as u32, js_elem)?; } - }) - .or_throw(&mut cx)?; + js_array + }; js_obj.set(&mut cx, "value", js_value)?; js_obj } @@ -12074,8 +13632,9 @@ fn workspace_decrypt_path_addr(mut cx: FunctionContext) -> JsResult { let js_obj = cx.empty_object(); let js_tag = JsBoolean::new(&mut cx, false); js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = - variant_workspace_decrypt_path_addr_error_rs_to_js(&mut cx, err)?; + let js_err = variant_workspace_history_stat_folder_children_error_rs_to_js( + &mut cx, err, + )?; js_obj.set(&mut cx, "error", js_err)?; js_obj } @@ -12087,8 +13646,8 @@ fn workspace_decrypt_path_addr(mut cx: FunctionContext) -> JsResult { Ok(promise) } -// workspace_generate_path_addr -fn workspace_generate_path_addr(mut cx: FunctionContext) -> JsResult { +// workspace_history_stat_folder_children_by_id +fn workspace_history_stat_folder_children_by_id(mut cx: FunctionContext) -> JsResult { crate::init_sentry(); let workspace = { let js_val = cx.argument::(0)?; @@ -12101,11 +13660,25 @@ fn workspace_generate_path_addr(mut cx: FunctionContext) -> JsResult v } }; - let path = { - let js_val = cx.argument::(1)?; + let at = { + let js_val = cx.argument::(1)?; { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) + let v = js_val.value(&mut cx); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + match custom_from_rs_f64(v) { + Ok(val) => val, + Err(err) => return cx.throw_type_error(err), + } + } + }; + let entry_id = { + let js_val = cx.argument::(2)?; + { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) }; match custom_from_rs_string(js_val.value(&mut cx)) { Ok(val) => val, @@ -12117,41 +13690,56 @@ fn workspace_generate_path_addr(mut cx: FunctionContext) -> JsResult let (deferred, promise) = cx.promise(); // TODO: Promises are not cancellable in Javascript by default, should we add a custom cancel method ? - let _handle = crate::TOKIO_RUNTIME.lock().expect("Mutex is poisoned").spawn(async move { - - let ret = libparsec::workspace_generate_path_addr( - workspace, - &path, - ).await; + let _handle = crate::TOKIO_RUNTIME + .lock() + .expect("Mutex is poisoned") + .spawn(async move { + let ret = + libparsec::workspace_history_stat_folder_children_by_id(workspace, at, entry_id) + .await; - deferred.settle_with(&channel, move |mut cx| { - let js_ret = match ret { - Ok(ok) => { - let js_obj = JsObject::new(&mut cx); - let js_tag = JsBoolean::new(&mut cx, true); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_value = JsString::try_new(&mut cx,{ - let custom_to_rs_string = |addr: libparsec::ParsecWorkspacePathAddr| -> Result { Ok(addr.to_url().into()) }; - match custom_to_rs_string(ok) { - Ok(ok) => ok, - Err(err) => return cx.throw_type_error(err), - } -}).or_throw(&mut cx)?; - js_obj.set(&mut cx, "value", js_value)?; - js_obj - } - Err(err) => { - let js_obj = cx.empty_object(); - let js_tag = JsBoolean::new(&mut cx, false); - js_obj.set(&mut cx, "ok", js_tag)?; - let js_err = variant_workspace_generate_path_addr_error_rs_to_js(&mut cx, err)?; - js_obj.set(&mut cx, "error", js_err)?; - js_obj - } -}; - Ok(js_ret) + deferred.settle_with(&channel, move |mut cx| { + let js_ret = match ret { + Ok(ok) => { + let js_obj = JsObject::new(&mut cx); + let js_tag = JsBoolean::new(&mut cx, true); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_value = { + // JsArray::new allocates with `undefined` value, that's why we `set` value + let js_array = JsArray::new(&mut cx, ok.len()); + for (i, elem) in ok.into_iter().enumerate() { + let js_elem = { + let (x0, x1) = elem; + let js_array = JsArray::new(&mut cx, 2); + let js_value = + JsString::try_new(&mut cx, x0).or_throw(&mut cx)?; + js_array.set(&mut cx, 0, js_value)?; + let js_value = + variant_workspace_history_entry_stat_rs_to_js(&mut cx, x1)?; + js_array.set(&mut cx, 1, js_value)?; + js_array + }; + js_array.set(&mut cx, i as u32, js_elem)?; + } + js_array + }; + js_obj.set(&mut cx, "value", js_value)?; + js_obj + } + Err(err) => { + let js_obj = cx.empty_object(); + let js_tag = JsBoolean::new(&mut cx, false); + js_obj.set(&mut cx, "ok", js_tag)?; + let js_err = variant_workspace_history_stat_folder_children_error_rs_to_js( + &mut cx, err, + )?; + js_obj.set(&mut cx, "error", js_err)?; + js_obj + } + }; + Ok(js_ret) + }); }); - }); Ok(promise) } @@ -13292,6 +14880,31 @@ pub fn register_meths(cx: &mut ModuleContext) -> NeonResult<()> { cx.export_function("workspaceCreateFolderAll", workspace_create_folder_all)?; cx.export_function("workspaceDecryptPathAddr", workspace_decrypt_path_addr)?; cx.export_function("workspaceGeneratePathAddr", workspace_generate_path_addr)?; + cx.export_function("workspaceHistoryFdClose", workspace_history_fd_close)?; + cx.export_function("workspaceHistoryFdRead", workspace_history_fd_read)?; + cx.export_function("workspaceHistoryFdStat", workspace_history_fd_stat)?; + cx.export_function( + "workspaceHistoryGetWorkspaceManifestV1Timestamp", + workspace_history_get_workspace_manifest_v1_timestamp, + )?; + cx.export_function("workspaceHistoryOpenFile", workspace_history_open_file)?; + cx.export_function( + "workspaceHistoryOpenFileById", + workspace_history_open_file_by_id, + )?; + cx.export_function("workspaceHistoryStatEntry", workspace_history_stat_entry)?; + cx.export_function( + "workspaceHistoryStatEntryById", + workspace_history_stat_entry_by_id, + )?; + cx.export_function( + "workspaceHistoryStatFolderChildren", + workspace_history_stat_folder_children, + )?; + cx.export_function( + "workspaceHistoryStatFolderChildrenById", + workspace_history_stat_folder_children_by_id, + )?; cx.export_function("workspaceInfo", workspace_info)?; cx.export_function("workspaceMount", workspace_mount)?; cx.export_function("workspaceMoveEntry", workspace_move_entry)?; diff --git a/bindings/web/src/meths.rs b/bindings/web/src/meths.rs index a9893716d18..ff63909e5f6 100644 --- a/bindings/web/src/meths.rs +++ b/bindings/web/src/meths.rs @@ -2525,6 +2525,130 @@ fn struct_user_info_rs_to_js(rs_obj: libparsec::UserInfo) -> Result Result { + let id = { + let js_val = Reflect::get(&obj, &"id".into())?; + js_val + .dyn_into::() + .ok() + .and_then(|s| s.as_string()) + .ok_or_else(|| TypeError::new("Not a string")) + .and_then(|x| { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(x).map_err(|e| TypeError::new(e.as_ref())) + }) + .map_err(|_| TypeError::new("Not a valid VlobID"))? + }; + let created = { + let js_val = Reflect::get(&obj, &"created".into())?; + { + let v = js_val.dyn_into::()?.value_of(); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + let v = custom_from_rs_f64(v).map_err(|e| TypeError::new(e.as_ref()))?; + v + } + }; + let updated = { + let js_val = Reflect::get(&obj, &"updated".into())?; + { + let v = js_val.dyn_into::()?.value_of(); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + let v = custom_from_rs_f64(v).map_err(|e| TypeError::new(e.as_ref()))?; + v + } + }; + let version = { + let js_val = Reflect::get(&obj, &"version".into())?; + { + let v = js_val + .dyn_into::() + .map_err(|_| TypeError::new("Not a number"))? + .value_of(); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + return Err(JsValue::from(TypeError::new("Not an u32 number"))); + } + v as u32 + } + }; + let size = { + let js_val = Reflect::get(&obj, &"size".into())?; + { + let v = js_val + .dyn_into::() + .map_err(|_| TypeError::new("Not a number"))? + .value_of(); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + return Err(JsValue::from(TypeError::new("Not an u64 number"))); + } + v as u64 + } + }; + Ok(libparsec::WorkspaceHistoryFileStat { + id, + created, + updated, + version, + size, + }) +} + +#[allow(dead_code)] +fn struct_workspace_history_file_stat_rs_to_js( + rs_obj: libparsec::WorkspaceHistoryFileStat, +) -> Result { + let js_obj = Object::new().into(); + let js_id = JsValue::from_str({ + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(rs_obj.id) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); + Reflect::set(&js_obj, &"id".into(), &js_id)?; + let js_created = { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + let v = match custom_to_rs_f64(rs_obj.created) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + }; + JsValue::from(v) + }; + Reflect::set(&js_obj, &"created".into(), &js_created)?; + let js_updated = { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + let v = match custom_to_rs_f64(rs_obj.updated) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + }; + JsValue::from(v) + }; + Reflect::set(&js_obj, &"updated".into(), &js_updated)?; + let js_version = JsValue::from(rs_obj.version); + Reflect::set(&js_obj, &"version".into(), &js_version)?; + let js_size = JsValue::from(rs_obj.size); + Reflect::set(&js_obj, &"size".into(), &js_size)?; + Ok(js_obj) +} + // WorkspaceInfo #[allow(dead_code)] @@ -7270,81 +7394,821 @@ fn variant_workspace_generate_path_addr_error_rs_to_js( Ok(js_obj) } -// WorkspaceInfoError - -#[allow(dead_code)] -fn variant_workspace_info_error_rs_to_js( - rs_obj: libparsec::WorkspaceInfoError, -) -> Result { - let js_obj = Object::new().into(); - let js_display = &rs_obj.to_string(); - Reflect::set(&js_obj, &"error".into(), &js_display.into())?; - match rs_obj { - libparsec::WorkspaceInfoError::Internal { .. } => { - Reflect::set(&js_obj, &"tag".into(), &"WorkspaceInfoErrorInternal".into())?; - } - } - Ok(js_obj) -} - -// WorkspaceMountError - -#[allow(dead_code)] -fn variant_workspace_mount_error_rs_to_js( - rs_obj: libparsec::WorkspaceMountError, -) -> Result { - let js_obj = Object::new().into(); - let js_display = &rs_obj.to_string(); - Reflect::set(&js_obj, &"error".into(), &js_display.into())?; - match rs_obj { - libparsec::WorkspaceMountError::Disabled { .. } => { - Reflect::set( - &js_obj, - &"tag".into(), - &"WorkspaceMountErrorDisabled".into(), - )?; - } - libparsec::WorkspaceMountError::Internal { .. } => { - Reflect::set( - &js_obj, - &"tag".into(), - &"WorkspaceMountErrorInternal".into(), - )?; - } - } - Ok(js_obj) -} - -// WorkspaceMoveEntryError +// WorkspaceHistoryEntryStat #[allow(dead_code)] -fn variant_workspace_move_entry_error_rs_to_js( - rs_obj: libparsec::WorkspaceMoveEntryError, -) -> Result { - let js_obj = Object::new().into(); - let js_display = &rs_obj.to_string(); - Reflect::set(&js_obj, &"error".into(), &js_display.into())?; - match rs_obj { - libparsec::WorkspaceMoveEntryError::CannotMoveRoot { .. } => { - Reflect::set( - &js_obj, - &"tag".into(), - &"WorkspaceMoveEntryErrorCannotMoveRoot".into(), - )?; - } - libparsec::WorkspaceMoveEntryError::DestinationExists { .. } => { - Reflect::set( - &js_obj, - &"tag".into(), - &"WorkspaceMoveEntryErrorDestinationExists".into(), - )?; - } - libparsec::WorkspaceMoveEntryError::DestinationNotFound { .. } => { - Reflect::set( - &js_obj, - &"tag".into(), - &"WorkspaceMoveEntryErrorDestinationNotFound".into(), - )?; +fn variant_workspace_history_entry_stat_js_to_rs( + obj: JsValue, +) -> Result { + let tag = Reflect::get(&obj, &"tag".into())?; + let tag = tag + .as_string() + .ok_or_else(|| JsValue::from(TypeError::new("tag isn't a string")))?; + match tag.as_str() { + "WorkspaceHistoryEntryStatFile" => { + let id = { + let js_val = Reflect::get(&obj, &"id".into())?; + js_val + .dyn_into::() + .ok() + .and_then(|s| s.as_string()) + .ok_or_else(|| TypeError::new("Not a string")) + .and_then(|x| { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(x).map_err(|e| TypeError::new(e.as_ref())) + }) + .map_err(|_| TypeError::new("Not a valid VlobID"))? + }; + let parent = { + let js_val = Reflect::get(&obj, &"parent".into())?; + js_val + .dyn_into::() + .ok() + .and_then(|s| s.as_string()) + .ok_or_else(|| TypeError::new("Not a string")) + .and_then(|x| { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(x).map_err(|e| TypeError::new(e.as_ref())) + }) + .map_err(|_| TypeError::new("Not a valid VlobID"))? + }; + let created = { + let js_val = Reflect::get(&obj, &"created".into())?; + { + let v = js_val.dyn_into::()?.value_of(); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + let v = custom_from_rs_f64(v).map_err(|e| TypeError::new(e.as_ref()))?; + v + } + }; + let updated = { + let js_val = Reflect::get(&obj, &"updated".into())?; + { + let v = js_val.dyn_into::()?.value_of(); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + let v = custom_from_rs_f64(v).map_err(|e| TypeError::new(e.as_ref()))?; + v + } + }; + let version = { + let js_val = Reflect::get(&obj, &"version".into())?; + { + let v = js_val + .dyn_into::() + .map_err(|_| TypeError::new("Not a number"))? + .value_of(); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + return Err(JsValue::from(TypeError::new("Not an u32 number"))); + } + v as u32 + } + }; + let size = { + let js_val = Reflect::get(&obj, &"size".into())?; + { + let v = js_val + .dyn_into::() + .map_err(|_| TypeError::new("Not a number"))? + .value_of(); + if v < (u64::MIN as f64) || (u64::MAX as f64) < v { + return Err(JsValue::from(TypeError::new("Not an u64 number"))); + } + v as u64 + } + }; + Ok(libparsec::WorkspaceHistoryEntryStat::File { + id, + parent, + created, + updated, + version, + size, + }) + } + "WorkspaceHistoryEntryStatFolder" => { + let id = { + let js_val = Reflect::get(&obj, &"id".into())?; + js_val + .dyn_into::() + .ok() + .and_then(|s| s.as_string()) + .ok_or_else(|| TypeError::new("Not a string")) + .and_then(|x| { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(x).map_err(|e| TypeError::new(e.as_ref())) + }) + .map_err(|_| TypeError::new("Not a valid VlobID"))? + }; + let parent = { + let js_val = Reflect::get(&obj, &"parent".into())?; + js_val + .dyn_into::() + .ok() + .and_then(|s| s.as_string()) + .ok_or_else(|| TypeError::new("Not a string")) + .and_then(|x| { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(x).map_err(|e| TypeError::new(e.as_ref())) + }) + .map_err(|_| TypeError::new("Not a valid VlobID"))? + }; + let created = { + let js_val = Reflect::get(&obj, &"created".into())?; + { + let v = js_val.dyn_into::()?.value_of(); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + let v = custom_from_rs_f64(v).map_err(|e| TypeError::new(e.as_ref()))?; + v + } + }; + let updated = { + let js_val = Reflect::get(&obj, &"updated".into())?; + { + let v = js_val.dyn_into::()?.value_of(); + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + let v = custom_from_rs_f64(v).map_err(|e| TypeError::new(e.as_ref()))?; + v + } + }; + let version = { + let js_val = Reflect::get(&obj, &"version".into())?; + { + let v = js_val + .dyn_into::() + .map_err(|_| TypeError::new("Not a number"))? + .value_of(); + if v < (u32::MIN as f64) || (u32::MAX as f64) < v { + return Err(JsValue::from(TypeError::new("Not an u32 number"))); + } + v as u32 + } + }; + Ok(libparsec::WorkspaceHistoryEntryStat::Folder { + id, + parent, + created, + updated, + version, + }) + } + _ => Err(JsValue::from(TypeError::new( + "Object is not a WorkspaceHistoryEntryStat", + ))), + } +} + +#[allow(dead_code)] +fn variant_workspace_history_entry_stat_rs_to_js( + rs_obj: libparsec::WorkspaceHistoryEntryStat, +) -> Result { + let js_obj = Object::new().into(); + match rs_obj { + libparsec::WorkspaceHistoryEntryStat::File { + id, + parent, + created, + updated, + version, + size, + .. + } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryEntryStatFile".into(), + )?; + let js_id = JsValue::from_str({ + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(id) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); + Reflect::set(&js_obj, &"id".into(), &js_id)?; + let js_parent = JsValue::from_str({ + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(parent) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); + Reflect::set(&js_obj, &"parent".into(), &js_parent)?; + let js_created = { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + let v = match custom_to_rs_f64(created) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + }; + JsValue::from(v) + }; + Reflect::set(&js_obj, &"created".into(), &js_created)?; + let js_updated = { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + let v = match custom_to_rs_f64(updated) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + }; + JsValue::from(v) + }; + Reflect::set(&js_obj, &"updated".into(), &js_updated)?; + let js_version = JsValue::from(version); + Reflect::set(&js_obj, &"version".into(), &js_version)?; + let js_size = JsValue::from(size); + Reflect::set(&js_obj, &"size".into(), &js_size)?; + } + libparsec::WorkspaceHistoryEntryStat::Folder { + id, + parent, + created, + updated, + version, + .. + } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryEntryStatFolder".into(), + )?; + let js_id = JsValue::from_str({ + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(id) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); + Reflect::set(&js_obj, &"id".into(), &js_id)?; + let js_parent = JsValue::from_str({ + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(parent) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); + Reflect::set(&js_obj, &"parent".into(), &js_parent)?; + let js_created = { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + let v = match custom_to_rs_f64(created) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + }; + JsValue::from(v) + }; + Reflect::set(&js_obj, &"created".into(), &js_created)?; + let js_updated = { + let custom_to_rs_f64 = |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + let v = match custom_to_rs_f64(updated) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + }; + JsValue::from(v) + }; + Reflect::set(&js_obj, &"updated".into(), &js_updated)?; + let js_version = JsValue::from(version); + Reflect::set(&js_obj, &"version".into(), &js_version)?; + } + } + Ok(js_obj) +} + +// WorkspaceHistoryFdCloseError + +#[allow(dead_code)] +fn variant_workspace_history_fd_close_error_rs_to_js( + rs_obj: libparsec::WorkspaceHistoryFdCloseError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceHistoryFdCloseError::BadFileDescriptor { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdCloseErrorBadFileDescriptor".into(), + )?; + } + libparsec::WorkspaceHistoryFdCloseError::Internal { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdCloseErrorInternal".into(), + )?; + } + } + Ok(js_obj) +} + +// WorkspaceHistoryFdReadError + +#[allow(dead_code)] +fn variant_workspace_history_fd_read_error_rs_to_js( + rs_obj: libparsec::WorkspaceHistoryFdReadError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceHistoryFdReadError::BadFileDescriptor { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdReadErrorBadFileDescriptor".into(), + )?; + } + libparsec::WorkspaceHistoryFdReadError::BlockNotFound { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdReadErrorBlockNotFound".into(), + )?; + } + libparsec::WorkspaceHistoryFdReadError::Internal { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdReadErrorInternal".into(), + )?; + } + libparsec::WorkspaceHistoryFdReadError::InvalidBlockAccess { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdReadErrorInvalidBlockAccess".into(), + )?; + } + libparsec::WorkspaceHistoryFdReadError::InvalidCertificate { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdReadErrorInvalidCertificate".into(), + )?; + } + libparsec::WorkspaceHistoryFdReadError::InvalidKeysBundle { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdReadErrorInvalidKeysBundle".into(), + )?; + } + libparsec::WorkspaceHistoryFdReadError::NoRealmAccess { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdReadErrorNoRealmAccess".into(), + )?; + } + libparsec::WorkspaceHistoryFdReadError::Offline { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdReadErrorOffline".into(), + )?; + } + libparsec::WorkspaceHistoryFdReadError::Stopped { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdReadErrorStopped".into(), + )?; + } + } + Ok(js_obj) +} + +// WorkspaceHistoryFdStatError + +#[allow(dead_code)] +fn variant_workspace_history_fd_stat_error_rs_to_js( + rs_obj: libparsec::WorkspaceHistoryFdStatError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceHistoryFdStatError::BadFileDescriptor { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdStatErrorBadFileDescriptor".into(), + )?; + } + libparsec::WorkspaceHistoryFdStatError::Internal { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryFdStatErrorInternal".into(), + )?; + } + } + Ok(js_obj) +} + +// WorkspaceHistoryGetWorkspaceManifestV1TimestampError + +#[allow(dead_code)] +fn variant_workspace_history_get_workspace_manifest_v1_timestamp_error_rs_to_js( + rs_obj: libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Internal { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInternal".into(), + )?; + } + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::InvalidCertificate { + .. + } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidCertificate".into(), + )?; + } + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::InvalidKeysBundle { + .. + } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidKeysBundle".into(), + )?; + } + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::InvalidManifest { + .. + } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidManifest".into(), + )?; + } + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::NoRealmAccess { + .. + } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorNoRealmAccess".into(), + )?; + } + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Offline { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorOffline".into(), + )?; + } + libparsec::WorkspaceHistoryGetWorkspaceManifestV1TimestampError::Stopped { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorStopped".into(), + )?; + } + } + Ok(js_obj) +} + +// WorkspaceHistoryOpenFileError + +#[allow(dead_code)] +fn variant_workspace_history_open_file_error_rs_to_js( + rs_obj: libparsec::WorkspaceHistoryOpenFileError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceHistoryOpenFileError::EntryNotAFile { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryOpenFileErrorEntryNotAFile".into(), + )?; + } + libparsec::WorkspaceHistoryOpenFileError::EntryNotFound { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryOpenFileErrorEntryNotFound".into(), + )?; + } + libparsec::WorkspaceHistoryOpenFileError::Internal { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryOpenFileErrorInternal".into(), + )?; + } + libparsec::WorkspaceHistoryOpenFileError::InvalidCertificate { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryOpenFileErrorInvalidCertificate".into(), + )?; + } + libparsec::WorkspaceHistoryOpenFileError::InvalidKeysBundle { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryOpenFileErrorInvalidKeysBundle".into(), + )?; + } + libparsec::WorkspaceHistoryOpenFileError::InvalidManifest { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryOpenFileErrorInvalidManifest".into(), + )?; + } + libparsec::WorkspaceHistoryOpenFileError::NoRealmAccess { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryOpenFileErrorNoRealmAccess".into(), + )?; + } + libparsec::WorkspaceHistoryOpenFileError::Offline { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryOpenFileErrorOffline".into(), + )?; + } + libparsec::WorkspaceHistoryOpenFileError::Stopped { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryOpenFileErrorStopped".into(), + )?; + } + } + Ok(js_obj) +} + +// WorkspaceHistoryStatEntryError + +#[allow(dead_code)] +fn variant_workspace_history_stat_entry_error_rs_to_js( + rs_obj: libparsec::WorkspaceHistoryStatEntryError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceHistoryStatEntryError::EntryNotFound { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatEntryErrorEntryNotFound".into(), + )?; + } + libparsec::WorkspaceHistoryStatEntryError::Internal { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatEntryErrorInternal".into(), + )?; + } + libparsec::WorkspaceHistoryStatEntryError::InvalidCertificate { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatEntryErrorInvalidCertificate".into(), + )?; + } + libparsec::WorkspaceHistoryStatEntryError::InvalidKeysBundle { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatEntryErrorInvalidKeysBundle".into(), + )?; + } + libparsec::WorkspaceHistoryStatEntryError::InvalidManifest { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatEntryErrorInvalidManifest".into(), + )?; + } + libparsec::WorkspaceHistoryStatEntryError::NoRealmAccess { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatEntryErrorNoRealmAccess".into(), + )?; + } + libparsec::WorkspaceHistoryStatEntryError::Offline { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatEntryErrorOffline".into(), + )?; + } + libparsec::WorkspaceHistoryStatEntryError::Stopped { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatEntryErrorStopped".into(), + )?; + } + } + Ok(js_obj) +} + +// WorkspaceHistoryStatFolderChildrenError + +#[allow(dead_code)] +fn variant_workspace_history_stat_folder_children_error_rs_to_js( + rs_obj: libparsec::WorkspaceHistoryStatFolderChildrenError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceHistoryStatFolderChildrenError::EntryIsFile { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatFolderChildrenErrorEntryIsFile".into(), + )?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::EntryNotFound { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatFolderChildrenErrorEntryNotFound".into(), + )?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::Internal { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatFolderChildrenErrorInternal".into(), + )?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::InvalidCertificate { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatFolderChildrenErrorInvalidCertificate".into(), + )?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::InvalidKeysBundle { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatFolderChildrenErrorInvalidKeysBundle".into(), + )?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::InvalidManifest { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatFolderChildrenErrorInvalidManifest".into(), + )?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::NoRealmAccess { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatFolderChildrenErrorNoRealmAccess".into(), + )?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::Offline { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatFolderChildrenErrorOffline".into(), + )?; + } + libparsec::WorkspaceHistoryStatFolderChildrenError::Stopped { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceHistoryStatFolderChildrenErrorStopped".into(), + )?; + } + } + Ok(js_obj) +} + +// WorkspaceInfoError + +#[allow(dead_code)] +fn variant_workspace_info_error_rs_to_js( + rs_obj: libparsec::WorkspaceInfoError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceInfoError::Internal { .. } => { + Reflect::set(&js_obj, &"tag".into(), &"WorkspaceInfoErrorInternal".into())?; + } + } + Ok(js_obj) +} + +// WorkspaceMountError + +#[allow(dead_code)] +fn variant_workspace_mount_error_rs_to_js( + rs_obj: libparsec::WorkspaceMountError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceMountError::Disabled { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceMountErrorDisabled".into(), + )?; + } + libparsec::WorkspaceMountError::Internal { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceMountErrorInternal".into(), + )?; + } + } + Ok(js_obj) +} + +// WorkspaceMoveEntryError + +#[allow(dead_code)] +fn variant_workspace_move_entry_error_rs_to_js( + rs_obj: libparsec::WorkspaceMoveEntryError, +) -> Result { + let js_obj = Object::new().into(); + let js_display = &rs_obj.to_string(); + Reflect::set(&js_obj, &"error".into(), &js_display.into())?; + match rs_obj { + libparsec::WorkspaceMoveEntryError::CannotMoveRoot { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceMoveEntryErrorCannotMoveRoot".into(), + )?; + } + libparsec::WorkspaceMoveEntryError::DestinationExists { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceMoveEntryErrorDestinationExists".into(), + )?; + } + libparsec::WorkspaceMoveEntryError::DestinationNotFound { .. } => { + Reflect::set( + &js_obj, + &"tag".into(), + &"WorkspaceMoveEntryErrorDestinationNotFound".into(), + )?; } libparsec::WorkspaceMoveEntryError::Internal { .. } => { Reflect::set( @@ -8005,14 +8869,221 @@ pub fn bootstrapOrganization( Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_available_device_rs_to_js(value)?; + let js_value = struct_available_device_rs_to_js(value)?; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_bootstrap_organization_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) + }) +} + +// build_parsec_organization_bootstrap_addr +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn buildParsecOrganizationBootstrapAddr(addr: String, organization_id: String) -> Promise { + future_to_promise(async move { + let addr = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::ParsecAddr::from_any(&s).map_err(|e| e.to_string()) + }; + custom_from_rs_string(addr).map_err(|e| TypeError::new(e.as_ref())) + }?; + let organization_id = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::OrganizationID::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(organization_id).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::build_parsec_organization_bootstrap_addr(addr, organization_id); + Ok(JsValue::from_str({ + let custom_to_rs_string = + |addr: libparsec::ParsecOrganizationBootstrapAddr| -> Result { + Ok(addr.to_url().into()) + }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + })) + }) +} + +// cancel +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn cancel(canceller: u32) -> Promise { + future_to_promise(async move { + let ret = libparsec::cancel(canceller); + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = { + let _ = value; + JsValue::null() + }; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_cancel_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) + }) +} + +// claimer_device_finalize_save_local_device +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn claimerDeviceFinalizeSaveLocalDevice(handle: u32, save_strategy: Object) -> Promise { + future_to_promise(async move { + let save_strategy = save_strategy.into(); + let save_strategy = variant_device_save_strategy_js_to_rs(save_strategy)?; + + let ret = libparsec::claimer_device_finalize_save_local_device(handle, save_strategy).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = struct_available_device_rs_to_js(value)?; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) + }) +} + +// claimer_device_in_progress_1_do_deny_trust +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn claimerDeviceInProgress1DoDenyTrust(canceller: u32, handle: u32) -> Promise { + future_to_promise(async move { + let ret = libparsec::claimer_device_in_progress_1_do_deny_trust(canceller, handle).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = { + let _ = value; + JsValue::null() + }; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) + }) +} + +// claimer_device_in_progress_1_do_signify_trust +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn claimerDeviceInProgress1DoSignifyTrust(canceller: u32, handle: u32) -> Promise { + future_to_promise(async move { + let ret = libparsec::claimer_device_in_progress_1_do_signify_trust(canceller, handle).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = struct_device_claim_in_progress2_info_rs_to_js(value)?; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) + }) +} + +// claimer_device_in_progress_2_do_wait_peer_trust +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn claimerDeviceInProgress2DoWaitPeerTrust(canceller: u32, handle: u32) -> Promise { + future_to_promise(async move { + let ret = + libparsec::claimer_device_in_progress_2_do_wait_peer_trust(canceller, handle).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = struct_device_claim_in_progress3_info_rs_to_js(value)?; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) + }) +} + +// claimer_device_in_progress_3_do_claim +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn claimerDeviceInProgress3DoClaim( + canceller: u32, + handle: u32, + requested_device_label: String, +) -> Promise { + future_to_promise(async move { + let requested_device_label = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(requested_device_label).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::claimer_device_in_progress_3_do_claim( + canceller, + handle, + requested_device_label, + ) + .await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = struct_device_claim_finalize_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_bootstrap_organization_error_rs_to_js(err)?; + let js_err = variant_claim_in_progress_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8020,44 +9091,37 @@ pub fn bootstrapOrganization( }) } -// build_parsec_organization_bootstrap_addr +// claimer_device_initial_do_wait_peer #[allow(non_snake_case)] #[wasm_bindgen] -pub fn buildParsecOrganizationBootstrapAddr(addr: String, organization_id: String) -> Promise { +pub fn claimerDeviceInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let addr = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::ParsecAddr::from_any(&s).map_err(|e| e.to_string()) - }; - custom_from_rs_string(addr).map_err(|e| TypeError::new(e.as_ref())) - }?; - let organization_id = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::OrganizationID::try_from(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(organization_id).map_err(|e| TypeError::new(e.as_ref())) - }?; - let ret = libparsec::build_parsec_organization_bootstrap_addr(addr, organization_id); - Ok(JsValue::from_str({ - let custom_to_rs_string = - |addr: libparsec::ParsecOrganizationBootstrapAddr| -> Result { - Ok(addr.to_url().into()) - }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + let ret = libparsec::claimer_device_initial_do_wait_peer(canceller, handle).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = struct_device_claim_in_progress1_info_rs_to_js(value)?; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj } - .as_ref() - })) + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) }) } -// cancel +// claimer_greeter_abort_operation #[allow(non_snake_case)] #[wasm_bindgen] -pub fn cancel(canceller: u32) -> Promise { +pub fn claimerGreeterAbortOperation(handle: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::cancel(canceller); + let ret = libparsec::claimer_greeter_abort_operation(handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -8072,7 +9136,7 @@ pub fn cancel(canceller: u32) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_cancel_error_rs_to_js(err)?; + let js_err = variant_claimer_greeter_abort_operation_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8080,15 +9144,61 @@ pub fn cancel(canceller: u32) -> Promise { }) } -// claimer_device_finalize_save_local_device +// claimer_retrieve_info #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerDeviceFinalizeSaveLocalDevice(handle: u32, save_strategy: Object) -> Promise { +pub fn claimerRetrieveInfo(config: Object, on_event_callback: Function, addr: String) -> Promise { + future_to_promise(async move { + let config = config.into(); + let config = struct_client_config_js_to_rs(config)?; + + let on_event_callback = std::sync::Arc::new( + move |handle: libparsec::Handle, event: libparsec::ClientEvent| { + // TODO: Better error handling here (log error ?) + let js_event = + variant_client_event_rs_to_js(event).expect("event type conversion error"); + on_event_callback + .call2(&JsValue::NULL, &Number::from(handle), &js_event) + .expect("error in event callback"); + }, + ) + as std::sync::Arc; + + let addr = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::ParsecInvitationAddr::from_any(&s).map_err(|e| e.to_string()) + }; + custom_from_rs_string(addr).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::claimer_retrieve_info(config, on_event_callback, addr).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = variant_user_or_device_claim_initial_info_rs_to_js(value)?; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_claimer_retrieve_info_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) + }) +} + +// claimer_user_finalize_save_local_device +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn claimerUserFinalizeSaveLocalDevice(handle: u32, save_strategy: Object) -> Promise { future_to_promise(async move { let save_strategy = save_strategy.into(); let save_strategy = variant_device_save_strategy_js_to_rs(save_strategy)?; - let ret = libparsec::claimer_device_finalize_save_local_device(handle, save_strategy).await; + let ret = libparsec::claimer_user_finalize_save_local_device(handle, save_strategy).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -8108,12 +9218,12 @@ pub fn claimerDeviceFinalizeSaveLocalDevice(handle: u32, save_strategy: Object) }) } -// claimer_device_in_progress_1_do_deny_trust +// claimer_user_in_progress_1_do_deny_trust #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerDeviceInProgress1DoDenyTrust(canceller: u32, handle: u32) -> Promise { +pub fn claimerUserInProgress1DoDenyTrust(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::claimer_device_in_progress_1_do_deny_trust(canceller, handle).await; + let ret = libparsec::claimer_user_in_progress_1_do_deny_trust(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -8136,17 +9246,17 @@ pub fn claimerDeviceInProgress1DoDenyTrust(canceller: u32, handle: u32) -> Promi }) } -// claimer_device_in_progress_1_do_signify_trust +// claimer_user_in_progress_1_do_signify_trust #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerDeviceInProgress1DoSignifyTrust(canceller: u32, handle: u32) -> Promise { +pub fn claimerUserInProgress1DoSignifyTrust(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::claimer_device_in_progress_1_do_signify_trust(canceller, handle).await; + let ret = libparsec::claimer_user_in_progress_1_do_signify_trust(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_device_claim_in_progress2_info_rs_to_js(value)?; + let js_value = struct_user_claim_in_progress2_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } @@ -8161,18 +9271,17 @@ pub fn claimerDeviceInProgress1DoSignifyTrust(canceller: u32, handle: u32) -> Pr }) } -// claimer_device_in_progress_2_do_wait_peer_trust +// claimer_user_in_progress_2_do_wait_peer_trust #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerDeviceInProgress2DoWaitPeerTrust(canceller: u32, handle: u32) -> Promise { +pub fn claimerUserInProgress2DoWaitPeerTrust(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let ret = - libparsec::claimer_device_in_progress_2_do_wait_peer_trust(canceller, handle).await; + let ret = libparsec::claimer_user_in_progress_2_do_wait_peer_trust(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_device_claim_in_progress3_info_rs_to_js(value)?; + let js_value = struct_user_claim_in_progress3_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } @@ -8187,13 +9296,14 @@ pub fn claimerDeviceInProgress2DoWaitPeerTrust(canceller: u32, handle: u32) -> P }) } -// claimer_device_in_progress_3_do_claim +// claimer_user_in_progress_3_do_claim #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerDeviceInProgress3DoClaim( +pub fn claimerUserInProgress3DoClaim( canceller: u32, handle: u32, requested_device_label: String, + requested_human_handle: Object, ) -> Promise { future_to_promise(async move { let requested_device_label = { @@ -8202,17 +9312,21 @@ pub fn claimerDeviceInProgress3DoClaim( }; custom_from_rs_string(requested_device_label).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::claimer_device_in_progress_3_do_claim( + let requested_human_handle = requested_human_handle.into(); + let requested_human_handle = struct_human_handle_js_to_rs(requested_human_handle)?; + + let ret = libparsec::claimer_user_in_progress_3_do_claim( canceller, handle, requested_device_label, + requested_human_handle, ) .await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_device_claim_finalize_info_rs_to_js(value)?; + let js_value = struct_user_claim_finalize_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } @@ -8227,17 +9341,17 @@ pub fn claimerDeviceInProgress3DoClaim( }) } -// claimer_device_initial_do_wait_peer +// claimer_user_initial_do_wait_peer #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerDeviceInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { +pub fn claimerUserInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::claimer_device_initial_do_wait_peer(canceller, handle).await; + let ret = libparsec::claimer_user_initial_do_wait_peer(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_device_claim_in_progress1_info_rs_to_js(value)?; + let js_value = struct_user_claim_in_progress1_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } @@ -8252,12 +9366,20 @@ pub fn claimerDeviceInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { }) } -// claimer_greeter_abort_operation +// client_accept_tos #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerGreeterAbortOperation(handle: u32) -> Promise { +pub fn clientAcceptTos(client: u32, tos_updated_on: f64) -> Promise { future_to_promise(async move { - let ret = libparsec::claimer_greeter_abort_operation(handle).await; + let tos_updated_on = { + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + custom_from_rs_f64(tos_updated_on).map_err(|e| TypeError::new(e.as_ref())) + }?; + + let ret = libparsec::client_accept_tos(client, tos_updated_on).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -8272,7 +9394,7 @@ pub fn claimerGreeterAbortOperation(handle: u32) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_claimer_greeter_abort_operation_error_rs_to_js(err)?; + let js_err = variant_client_accept_tos_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8280,45 +9402,33 @@ pub fn claimerGreeterAbortOperation(handle: u32) -> Promise { }) } -// claimer_retrieve_info +// client_cancel_invitation #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerRetrieveInfo(config: Object, on_event_callback: Function, addr: String) -> Promise { +pub fn clientCancelInvitation(client: u32, token: String) -> Promise { future_to_promise(async move { - let config = config.into(); - let config = struct_client_config_js_to_rs(config)?; - - let on_event_callback = std::sync::Arc::new( - move |handle: libparsec::Handle, event: libparsec::ClientEvent| { - // TODO: Better error handling here (log error ?) - let js_event = - variant_client_event_rs_to_js(event).expect("event type conversion error"); - on_event_callback - .call2(&JsValue::NULL, &Number::from(handle), &js_event) - .expect("error in event callback"); - }, - ) - as std::sync::Arc; - - let addr = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::ParsecInvitationAddr::from_any(&s).map_err(|e| e.to_string()) + let token = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) }; - custom_from_rs_string(addr).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_string(token).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::claimer_retrieve_info(config, on_event_callback, addr).await; + let ret = libparsec::client_cancel_invitation(client, token).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = variant_user_or_device_claim_initial_info_rs_to_js(value)?; + let js_value = { + let _ = value; + JsValue::null() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_claimer_retrieve_info_error_rs_to_js(err)?; + let js_err = variant_client_cancel_invitation_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8326,27 +9436,41 @@ pub fn claimerRetrieveInfo(config: Object, on_event_callback: Function, addr: St }) } -// claimer_user_finalize_save_local_device +// client_change_authentication #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerUserFinalizeSaveLocalDevice(handle: u32, save_strategy: Object) -> Promise { +pub fn clientChangeAuthentication( + client_config: Object, + current_auth: Object, + new_auth: Object, +) -> Promise { future_to_promise(async move { - let save_strategy = save_strategy.into(); - let save_strategy = variant_device_save_strategy_js_to_rs(save_strategy)?; + let client_config = client_config.into(); + let client_config = struct_client_config_js_to_rs(client_config)?; - let ret = libparsec::claimer_user_finalize_save_local_device(handle, save_strategy).await; + let current_auth = current_auth.into(); + let current_auth = variant_device_access_strategy_js_to_rs(current_auth)?; + + let new_auth = new_auth.into(); + let new_auth = variant_device_save_strategy_js_to_rs(new_auth)?; + + let ret = + libparsec::client_change_authentication(client_config, current_auth, new_auth).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_available_device_rs_to_js(value)?; + let js_value = { + let _ = value; + JsValue::null() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + let js_err = variant_client_change_authentication_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8354,27 +9478,38 @@ pub fn claimerUserFinalizeSaveLocalDevice(handle: u32, save_strategy: Object) -> }) } -// claimer_user_in_progress_1_do_deny_trust +// client_create_workspace #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerUserInProgress1DoDenyTrust(canceller: u32, handle: u32) -> Promise { +pub fn clientCreateWorkspace(client: u32, name: String) -> Promise { future_to_promise(async move { - let ret = libparsec::claimer_user_in_progress_1_do_deny_trust(canceller, handle).await; + let name = { + let custom_from_rs_string = |s: String| -> Result<_, _> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(name).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::client_create_workspace(client, name).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - let _ = value; - JsValue::null() - }; + let js_value = JsValue::from_str({ + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(value) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + let js_err = variant_client_create_workspace_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8382,24 +9517,24 @@ pub fn claimerUserInProgress1DoDenyTrust(canceller: u32, handle: u32) -> Promise }) } -// claimer_user_in_progress_1_do_signify_trust +// client_get_tos #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerUserInProgress1DoSignifyTrust(canceller: u32, handle: u32) -> Promise { +pub fn clientGetTos(client: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::claimer_user_in_progress_1_do_signify_trust(canceller, handle).await; + let ret = libparsec::client_get_tos(client).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_user_claim_in_progress2_info_rs_to_js(value)?; + let js_value = struct_tos_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + let js_err = variant_client_get_tos_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8407,24 +9542,38 @@ pub fn claimerUserInProgress1DoSignifyTrust(canceller: u32, handle: u32) -> Prom }) } -// claimer_user_in_progress_2_do_wait_peer_trust +// client_get_user_device #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerUserInProgress2DoWaitPeerTrust(canceller: u32, handle: u32) -> Promise { +pub fn clientGetUserDevice(client: u32, device: String) -> Promise { future_to_promise(async move { - let ret = libparsec::claimer_user_in_progress_2_do_wait_peer_trust(canceller, handle).await; + let device = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::DeviceID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(device).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::client_get_user_device(client, device).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_user_claim_in_progress3_info_rs_to_js(value)?; + let js_value = { + let (x1, x2) = value; + let js_array = Array::new_with_length(2); + let js_value = struct_user_info_rs_to_js(x1)?; + js_array.push(&js_value); + let js_value = struct_device_info_rs_to_js(x2)?; + js_array.push(&js_value); + js_array.into() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + let js_err = variant_client_get_user_device_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8432,44 +9581,24 @@ pub fn claimerUserInProgress2DoWaitPeerTrust(canceller: u32, handle: u32) -> Pro }) } -// claimer_user_in_progress_3_do_claim +// client_info #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerUserInProgress3DoClaim( - canceller: u32, - handle: u32, - requested_device_label: String, - requested_human_handle: Object, -) -> Promise { +pub fn clientInfo(client: u32) -> Promise { future_to_promise(async move { - let requested_device_label = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(requested_device_label).map_err(|e| TypeError::new(e.as_ref())) - }?; - let requested_human_handle = requested_human_handle.into(); - let requested_human_handle = struct_human_handle_js_to_rs(requested_human_handle)?; - - let ret = libparsec::claimer_user_in_progress_3_do_claim( - canceller, - handle, - requested_device_label, - requested_human_handle, - ) - .await; + let ret = libparsec::client_info(client).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_user_claim_finalize_info_rs_to_js(value)?; + let js_value = struct_client_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + let js_err = variant_client_info_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8477,24 +9606,32 @@ pub fn claimerUserInProgress3DoClaim( }) } -// claimer_user_initial_do_wait_peer +// client_list_invitations #[allow(non_snake_case)] #[wasm_bindgen] -pub fn claimerUserInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { +pub fn clientListInvitations(client: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::claimer_user_initial_do_wait_peer(canceller, handle).await; + let ret = libparsec::client_list_invitations(client).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_user_claim_in_progress1_info_rs_to_js(value)?; + let js_value = { + // Array::new_with_length allocates with `undefined` value, that's why we `set` value + let js_array = Array::new_with_length(value.len() as u32); + for (i, elem) in value.into_iter().enumerate() { + let js_elem = variant_invite_list_item_rs_to_js(elem)?; + js_array.set(i as u32, js_elem); + } + js_array.into() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_claim_in_progress_error_rs_to_js(err)?; + let js_err = variant_list_invitations_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8502,27 +9639,30 @@ pub fn claimerUserInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { }) } -// client_accept_tos +// client_list_user_devices #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientAcceptTos(client: u32, tos_updated_on: f64) -> Promise { +pub fn clientListUserDevices(client: u32, user: String) -> Promise { future_to_promise(async move { - let tos_updated_on = { - let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { - libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) - .map_err(|_| "Out-of-bound datetime") + let user = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) }; - custom_from_rs_f64(tos_updated_on).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_string(user).map_err(|e| TypeError::new(e.as_ref())) }?; - - let ret = libparsec::client_accept_tos(client, tos_updated_on).await; + let ret = libparsec::client_list_user_devices(client, user).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; let js_value = { - let _ = value; - JsValue::null() + // Array::new_with_length allocates with `undefined` value, that's why we `set` value + let js_array = Array::new_with_length(value.len() as u32); + for (i, elem) in value.into_iter().enumerate() { + let js_elem = struct_device_info_rs_to_js(elem)?; + js_array.set(i as u32, js_elem); + } + js_array.into() }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj @@ -8530,7 +9670,7 @@ pub fn clientAcceptTos(client: u32, tos_updated_on: f64) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_accept_tos_error_rs_to_js(err)?; + let js_err = variant_client_list_user_devices_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8538,25 +9678,24 @@ pub fn clientAcceptTos(client: u32, tos_updated_on: f64) -> Promise { }) } -// client_cancel_invitation +// client_list_users #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientCancelInvitation(client: u32, token: String) -> Promise { +pub fn clientListUsers(client: u32, skip_revoked: bool) -> Promise { future_to_promise(async move { - let token = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(token).map_err(|e| TypeError::new(e.as_ref())) - }?; - let ret = libparsec::client_cancel_invitation(client, token).await; + let ret = libparsec::client_list_users(client, skip_revoked).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; let js_value = { - let _ = value; - JsValue::null() + // Array::new_with_length allocates with `undefined` value, that's why we `set` value + let js_array = Array::new_with_length(value.len() as u32); + for (i, elem) in value.into_iter().enumerate() { + let js_elem = struct_user_info_rs_to_js(elem)?; + js_array.set(i as u32, js_elem); + } + js_array.into() }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj @@ -8564,7 +9703,7 @@ pub fn clientCancelInvitation(client: u32, token: String) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_cancel_invitation_error_rs_to_js(err)?; + let js_err = variant_client_list_users_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8572,33 +9711,30 @@ pub fn clientCancelInvitation(client: u32, token: String) -> Promise { }) } -// client_change_authentication +// client_list_workspace_users #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientChangeAuthentication( - client_config: Object, - current_auth: Object, - new_auth: Object, -) -> Promise { +pub fn clientListWorkspaceUsers(client: u32, realm_id: String) -> Promise { future_to_promise(async move { - let client_config = client_config.into(); - let client_config = struct_client_config_js_to_rs(client_config)?; - - let current_auth = current_auth.into(); - let current_auth = variant_device_access_strategy_js_to_rs(current_auth)?; - - let new_auth = new_auth.into(); - let new_auth = variant_device_save_strategy_js_to_rs(new_auth)?; - - let ret = - libparsec::client_change_authentication(client_config, current_auth, new_auth).await; + let realm_id = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(realm_id).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::client_list_workspace_users(client, realm_id).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; let js_value = { - let _ = value; - JsValue::null() + // Array::new_with_length allocates with `undefined` value, that's why we `set` value + let js_array = Array::new_with_length(value.len() as u32); + for (i, elem) in value.into_iter().enumerate() { + let js_elem = struct_workspace_user_access_info_rs_to_js(elem)?; + js_array.set(i as u32, js_elem); + } + js_array.into() }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj @@ -8606,7 +9742,7 @@ pub fn clientChangeAuthentication( Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_change_authentication_error_rs_to_js(err)?; + let js_err = variant_client_list_workspace_users_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8614,38 +9750,32 @@ pub fn clientChangeAuthentication( }) } -// client_create_workspace +// client_list_workspaces #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientCreateWorkspace(client: u32, name: String) -> Promise { +pub fn clientListWorkspaces(client: u32) -> Promise { future_to_promise(async move { - let name = { - let custom_from_rs_string = |s: String| -> Result<_, _> { - s.parse::().map_err(|e| e.to_string()) - }; - custom_from_rs_string(name).map_err(|e| TypeError::new(e.as_ref())) - }?; - let ret = libparsec::client_create_workspace(client, name).await; + let ret = libparsec::client_list_workspaces(client).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from_str({ - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; - match custom_to_rs_string(value) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + let js_value = { + // Array::new_with_length allocates with `undefined` value, that's why we `set` value + let js_array = Array::new_with_length(value.len() as u32); + for (i, elem) in value.into_iter().enumerate() { + let js_elem = struct_workspace_info_rs_to_js(elem)?; + js_array.set(i as u32, js_elem); } - .as_ref() - }); + js_array.into() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_create_workspace_error_rs_to_js(err)?; + let js_err = variant_client_list_workspaces_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8653,24 +9783,24 @@ pub fn clientCreateWorkspace(client: u32, name: String) -> Promise { }) } -// client_get_tos +// client_new_device_invitation #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientGetTos(client: u32) -> Promise { +pub fn clientNewDeviceInvitation(client: u32, send_email: bool) -> Promise { future_to_promise(async move { - let ret = libparsec::client_get_tos(client).await; + let ret = libparsec::client_new_device_invitation(client, send_email).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_tos_rs_to_js(value)?; + let js_value = struct_new_invitation_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_get_tos_error_rs_to_js(err)?; + let js_err = variant_client_new_device_invitation_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8678,38 +9808,24 @@ pub fn clientGetTos(client: u32) -> Promise { }) } -// client_get_user_device +// client_new_user_invitation #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientGetUserDevice(client: u32, device: String) -> Promise { +pub fn clientNewUserInvitation(client: u32, claimer_email: String, send_email: bool) -> Promise { future_to_promise(async move { - let device = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::DeviceID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(device).map_err(|e| TypeError::new(e.as_ref())) - }?; - let ret = libparsec::client_get_user_device(client, device).await; + let ret = libparsec::client_new_user_invitation(client, claimer_email, send_email).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - let (x1, x2) = value; - let js_array = Array::new_with_length(2); - let js_value = struct_user_info_rs_to_js(x1)?; - js_array.push(&js_value); - let js_value = struct_device_info_rs_to_js(x2)?; - js_array.push(&js_value); - js_array.into() - }; + let js_value = struct_new_invitation_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_get_user_device_error_rs_to_js(err)?; + let js_err = variant_client_new_user_invitation_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8717,24 +9833,39 @@ pub fn clientGetUserDevice(client: u32, device: String) -> Promise { }) } -// client_info +// client_rename_workspace #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientInfo(client: u32) -> Promise { +pub fn clientRenameWorkspace(client: u32, realm_id: String, new_name: String) -> Promise { future_to_promise(async move { - let ret = libparsec::client_info(client).await; + let realm_id = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(realm_id).map_err(|e| TypeError::new(e.as_ref())) + }?; + let new_name = { + let custom_from_rs_string = |s: String| -> Result<_, _> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(new_name).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::client_rename_workspace(client, realm_id, new_name).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_client_info_rs_to_js(value)?; + let js_value = { + let _ = value; + JsValue::null() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_info_error_rs_to_js(err)?; + let js_err = variant_client_rename_workspace_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8742,24 +9873,25 @@ pub fn clientInfo(client: u32) -> Promise { }) } -// client_list_invitations +// client_revoke_user #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientListInvitations(client: u32) -> Promise { +pub fn clientRevokeUser(client: u32, user: String) -> Promise { future_to_promise(async move { - let ret = libparsec::client_list_invitations(client).await; + let user = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(user).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::client_revoke_user(client, user).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; let js_value = { - // Array::new_with_length allocates with `undefined` value, that's why we `set` value - let js_array = Array::new_with_length(value.len() as u32); - for (i, elem) in value.into_iter().enumerate() { - let js_elem = variant_invite_list_item_rs_to_js(elem)?; - js_array.set(i as u32, js_elem); - } - js_array.into() + let _ = value; + JsValue::null() }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj @@ -8767,7 +9899,7 @@ pub fn clientListInvitations(client: u32) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_list_invitations_error_rs_to_js(err)?; + let js_err = variant_client_revoke_user_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8775,30 +9907,45 @@ pub fn clientListInvitations(client: u32) -> Promise { }) } -// client_list_user_devices +// client_share_workspace #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientListUserDevices(client: u32, user: String) -> Promise { +pub fn clientShareWorkspace( + client: u32, + realm_id: String, + recipient: String, + role: Option, +) -> Promise { future_to_promise(async move { - let user = { + let realm_id = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(realm_id).map_err(|e| TypeError::new(e.as_ref())) + }?; + let recipient = { let custom_from_rs_string = |s: String| -> Result { libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) }; - custom_from_rs_string(user).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_string(recipient).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::client_list_user_devices(client, user).await; + let role = match role { + Some(role) => { + let role = enum_realm_role_js_to_rs(&role)?; + + Some(role) + } + None => None, + }; + + let ret = libparsec::client_share_workspace(client, realm_id, recipient, role).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; let js_value = { - // Array::new_with_length allocates with `undefined` value, that's why we `set` value - let js_array = Array::new_with_length(value.len() as u32); - for (i, elem) in value.into_iter().enumerate() { - let js_elem = struct_device_info_rs_to_js(elem)?; - js_array.set(i as u32, js_elem); - } - js_array.into() + let _ = value; + JsValue::null() }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj @@ -8806,7 +9953,7 @@ pub fn clientListUserDevices(client: u32, user: String) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_list_user_devices_error_rs_to_js(err)?; + let js_err = variant_client_share_workspace_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8814,32 +9961,42 @@ pub fn clientListUserDevices(client: u32, user: String) -> Promise { }) } -// client_list_users +// client_start #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientListUsers(client: u32, skip_revoked: bool) -> Promise { +pub fn clientStart(config: Object, on_event_callback: Function, access: Object) -> Promise { future_to_promise(async move { - let ret = libparsec::client_list_users(client, skip_revoked).await; + let config = config.into(); + let config = struct_client_config_js_to_rs(config)?; + + let on_event_callback = std::sync::Arc::new( + move |handle: libparsec::Handle, event: libparsec::ClientEvent| { + // TODO: Better error handling here (log error ?) + let js_event = + variant_client_event_rs_to_js(event).expect("event type conversion error"); + on_event_callback + .call2(&JsValue::NULL, &Number::from(handle), &js_event) + .expect("error in event callback"); + }, + ) + as std::sync::Arc; + + let access = access.into(); + let access = variant_device_access_strategy_js_to_rs(access)?; + + let ret = libparsec::client_start(config, on_event_callback, access).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - // Array::new_with_length allocates with `undefined` value, that's why we `set` value - let js_array = Array::new_with_length(value.len() as u32); - for (i, elem) in value.into_iter().enumerate() { - let js_elem = struct_user_info_rs_to_js(elem)?; - js_array.set(i as u32, js_elem); - } - js_array.into() - }; + let js_value = JsValue::from(value); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_list_users_error_rs_to_js(err)?; + let js_err = variant_client_start_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8847,38 +10004,30 @@ pub fn clientListUsers(client: u32, skip_revoked: bool) -> Promise { }) } -// client_list_workspace_users +// client_start_device_invitation_greet #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientListWorkspaceUsers(client: u32, realm_id: String) -> Promise { +pub fn clientStartDeviceInvitationGreet(client: u32, token: String) -> Promise { future_to_promise(async move { - let realm_id = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + let token = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) }; - custom_from_rs_string(realm_id).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_string(token).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::client_list_workspace_users(client, realm_id).await; + let ret = libparsec::client_start_device_invitation_greet(client, token).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - // Array::new_with_length allocates with `undefined` value, that's why we `set` value - let js_array = Array::new_with_length(value.len() as u32); - for (i, elem) in value.into_iter().enumerate() { - let js_elem = struct_workspace_user_access_info_rs_to_js(elem)?; - js_array.set(i as u32, js_elem); - } - js_array.into() - }; + let js_value = struct_device_greet_initial_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_list_workspace_users_error_rs_to_js(err)?; + let js_err = variant_client_start_invitation_greet_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8886,32 +10035,30 @@ pub fn clientListWorkspaceUsers(client: u32, realm_id: String) -> Promise { }) } -// client_list_workspaces +// client_start_user_invitation_greet #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientListWorkspaces(client: u32) -> Promise { +pub fn clientStartUserInvitationGreet(client: u32, token: String) -> Promise { future_to_promise(async move { - let ret = libparsec::client_list_workspaces(client).await; + let token = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(token).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::client_start_user_invitation_greet(client, token).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - // Array::new_with_length allocates with `undefined` value, that's why we `set` value - let js_array = Array::new_with_length(value.len() as u32); - for (i, elem) in value.into_iter().enumerate() { - let js_elem = struct_workspace_info_rs_to_js(elem)?; - js_array.set(i as u32, js_elem); - } - js_array.into() - }; + let js_value = struct_user_greet_initial_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_list_workspaces_error_rs_to_js(err)?; + let js_err = variant_client_start_invitation_greet_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8919,24 +10066,30 @@ pub fn clientListWorkspaces(client: u32) -> Promise { }) } -// client_new_device_invitation +// client_start_workspace #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientNewDeviceInvitation(client: u32, send_email: bool) -> Promise { +pub fn clientStartWorkspace(client: u32, realm_id: String) -> Promise { future_to_promise(async move { - let ret = libparsec::client_new_device_invitation(client, send_email).await; + let realm_id = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(realm_id).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::client_start_workspace(client, realm_id).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_new_invitation_info_rs_to_js(value)?; + let js_value = JsValue::from(value); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_new_device_invitation_error_rs_to_js(err)?; + let js_err = variant_client_start_workspace_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8944,24 +10097,27 @@ pub fn clientNewDeviceInvitation(client: u32, send_email: bool) -> Promise { }) } -// client_new_user_invitation +// client_stop #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientNewUserInvitation(client: u32, claimer_email: String, send_email: bool) -> Promise { +pub fn clientStop(client: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::client_new_user_invitation(client, claimer_email, send_email).await; + let ret = libparsec::client_stop(client).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_new_invitation_info_rs_to_js(value)?; + let js_value = { + let _ = value; + JsValue::null() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_new_user_invitation_error_rs_to_js(err)?; + let js_err = variant_client_stop_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -8969,24 +10125,18 @@ pub fn clientNewUserInvitation(client: u32, claimer_email: String, send_email: b }) } -// client_rename_workspace +// fd_close #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientRenameWorkspace(client: u32, realm_id: String, new_name: String) -> Promise { +pub fn fdClose(workspace: u32, fd: u32) -> Promise { future_to_promise(async move { - let realm_id = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(realm_id).map_err(|e| TypeError::new(e.as_ref())) - }?; - let new_name = { - let custom_from_rs_string = |s: String| -> Result<_, _> { - s.parse::().map_err(|e| e.to_string()) - }; - custom_from_rs_string(new_name).map_err(|e| TypeError::new(e.as_ref())) + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::client_rename_workspace(client, realm_id, new_name).await; + + let ret = libparsec::fd_close(workspace, fd).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -9001,7 +10151,7 @@ pub fn clientRenameWorkspace(client: u32, realm_id: String, new_name: String) -> Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_rename_workspace_error_rs_to_js(err)?; + let js_err = variant_workspace_fd_close_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9009,18 +10159,18 @@ pub fn clientRenameWorkspace(client: u32, realm_id: String, new_name: String) -> }) } -// client_revoke_user +// fd_flush #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientRevokeUser(client: u32, user: String) -> Promise { +pub fn fdFlush(workspace: u32, fd: u32) -> Promise { future_to_promise(async move { - let user = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(user).map_err(|e| TypeError::new(e.as_ref())) + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::client_revoke_user(client, user).await; + + let ret = libparsec::fd_flush(workspace, fd).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -9035,7 +10185,7 @@ pub fn clientRevokeUser(client: u32, user: String) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_revoke_user_error_rs_to_js(err)?; + let js_err = variant_workspace_fd_flush_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9043,53 +10193,30 @@ pub fn clientRevokeUser(client: u32, user: String) -> Promise { }) } -// client_share_workspace -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn clientShareWorkspace( - client: u32, - realm_id: String, - recipient: String, - role: Option, -) -> Promise { - future_to_promise(async move { - let realm_id = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(realm_id).map_err(|e| TypeError::new(e.as_ref())) - }?; - let recipient = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::UserID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(recipient).map_err(|e| TypeError::new(e.as_ref())) - }?; - let role = match role { - Some(role) => { - let role = enum_realm_role_js_to_rs(&role)?; - - Some(role) - } - None => None, - }; +// fd_read +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn fdRead(workspace: u32, fd: u32, offset: u64, size: u64) -> Promise { + future_to_promise(async move { + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) + }?; - let ret = libparsec::client_share_workspace(client, realm_id, recipient, role).await; + let ret = libparsec::fd_read(workspace, fd, offset, size).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - let _ = value; - JsValue::null() - }; + let js_value = JsValue::from(Uint8Array::from(value.as_ref())); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_share_workspace_error_rs_to_js(err)?; + let js_err = variant_workspace_fd_read_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9097,42 +10224,33 @@ pub fn clientShareWorkspace( }) } -// client_start +// fd_resize #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientStart(config: Object, on_event_callback: Function, access: Object) -> Promise { +pub fn fdResize(workspace: u32, fd: u32, length: u64, truncate_only: bool) -> Promise { future_to_promise(async move { - let config = config.into(); - let config = struct_client_config_js_to_rs(config)?; - - let on_event_callback = std::sync::Arc::new( - move |handle: libparsec::Handle, event: libparsec::ClientEvent| { - // TODO: Better error handling here (log error ?) - let js_event = - variant_client_event_rs_to_js(event).expect("event type conversion error"); - on_event_callback - .call2(&JsValue::NULL, &Number::from(handle), &js_event) - .expect("error in event callback"); - }, - ) - as std::sync::Arc; - - let access = access.into(); - let access = variant_device_access_strategy_js_to_rs(access)?; + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) + }?; - let ret = libparsec::client_start(config, on_event_callback, access).await; + let ret = libparsec::fd_resize(workspace, fd, length, truncate_only).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from(value); + let js_value = { + let _ = value; + JsValue::null() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_start_error_rs_to_js(err)?; + let js_err = variant_workspace_fd_resize_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9140,30 +10258,32 @@ pub fn clientStart(config: Object, on_event_callback: Function, access: Object) }) } -// client_start_device_invitation_greet +// fd_write #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientStartDeviceInvitationGreet(client: u32, token: String) -> Promise { +pub fn fdWrite(workspace: u32, fd: u32, offset: u64, data: Uint8Array) -> Promise { future_to_promise(async move { - let token = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(token).map_err(|e| TypeError::new(e.as_ref())) + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::client_start_device_invitation_greet(client, token).await; + + let data = data.to_vec(); + + let ret = libparsec::fd_write(workspace, fd, offset, &data.to_vec()).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_device_greet_initial_info_rs_to_js(value)?; + let js_value = JsValue::from(value); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_start_invitation_greet_error_rs_to_js(err)?; + let js_err = variant_workspace_fd_write_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9171,30 +10291,32 @@ pub fn clientStartDeviceInvitationGreet(client: u32, token: String) -> Promise { }) } -// client_start_user_invitation_greet +// fd_write_constrained_io #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientStartUserInvitationGreet(client: u32, token: String) -> Promise { +pub fn fdWriteConstrainedIo(workspace: u32, fd: u32, offset: u64, data: Uint8Array) -> Promise { future_to_promise(async move { - let token = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::InvitationToken::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(token).map_err(|e| TypeError::new(e.as_ref())) + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::client_start_user_invitation_greet(client, token).await; + + let data = data.to_vec(); + + let ret = libparsec::fd_write_constrained_io(workspace, fd, offset, &data.to_vec()).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_user_greet_initial_info_rs_to_js(value)?; + let js_value = JsValue::from(value); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_start_invitation_greet_error_rs_to_js(err)?; + let js_err = variant_workspace_fd_write_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9202,18 +10324,20 @@ pub fn clientStartUserInvitationGreet(client: u32, token: String) -> Promise { }) } -// client_start_workspace +// fd_write_start_eof #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientStartWorkspace(client: u32, realm_id: String) -> Promise { +pub fn fdWriteStartEof(workspace: u32, fd: u32, data: Uint8Array) -> Promise { future_to_promise(async move { - let realm_id = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(realm_id).map_err(|e| TypeError::new(e.as_ref())) + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::client_start_workspace(client, realm_id).await; + + let data = data.to_vec(); + + let ret = libparsec::fd_write_start_eof(workspace, fd, &data.to_vec()).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -9225,7 +10349,7 @@ pub fn clientStartWorkspace(client: u32, realm_id: String) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_start_workspace_error_rs_to_js(err)?; + let js_err = variant_workspace_fd_write_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9233,27 +10357,98 @@ pub fn clientStartWorkspace(client: u32, realm_id: String) -> Promise { }) } -// client_stop +// get_default_config_dir #[allow(non_snake_case)] #[wasm_bindgen] -pub fn clientStop(client: u32) -> Promise { +pub fn getDefaultConfigDir() -> Promise { future_to_promise(async move { - let ret = libparsec::client_stop(client).await; + let ret = libparsec::get_default_config_dir(); + Ok(JsValue::from_str({ + let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { + path.into_os_string() + .into_string() + .map_err(|_| "Path contains non-utf8 characters") + }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + })) + }) +} + +// get_default_data_base_dir +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn getDefaultDataBaseDir() -> Promise { + future_to_promise(async move { + let ret = libparsec::get_default_data_base_dir(); + Ok(JsValue::from_str({ + let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { + path.into_os_string() + .into_string() + .map_err(|_| "Path contains non-utf8 characters") + }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + })) + }) +} + +// get_default_mountpoint_base_dir +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn getDefaultMountpointBaseDir() -> Promise { + future_to_promise(async move { + let ret = libparsec::get_default_mountpoint_base_dir(); + Ok(JsValue::from_str({ + let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { + path.into_os_string() + .into_string() + .map_err(|_| "Path contains non-utf8 characters") + }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + })) + }) +} + +// get_platform +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn getPlatform() -> Promise { + future_to_promise(async move { + let ret = libparsec::get_platform(); + Ok(JsValue::from_str(enum_platform_rs_to_js(ret))) + }) +} + +// greeter_device_in_progress_1_do_wait_peer_trust +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn greeterDeviceInProgress1DoWaitPeerTrust(canceller: u32, handle: u32) -> Promise { + future_to_promise(async move { + let ret = + libparsec::greeter_device_in_progress_1_do_wait_peer_trust(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - let _ = value; - JsValue::null() - }; + let js_value = struct_device_greet_in_progress2_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_client_stop_error_rs_to_js(err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9261,18 +10456,12 @@ pub fn clientStop(client: u32) -> Promise { }) } -// fd_close +// greeter_device_in_progress_2_do_deny_trust #[allow(non_snake_case)] #[wasm_bindgen] -pub fn fdClose(workspace: u32, fd: u32) -> Promise { +pub fn greeterDeviceInProgress2DoDenyTrust(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let fd = { - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) - }?; - - let ret = libparsec::fd_close(workspace, fd).await; + let ret = libparsec::greeter_device_in_progress_2_do_deny_trust(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -9287,7 +10476,7 @@ pub fn fdClose(workspace: u32, fd: u32) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_fd_close_error_rs_to_js(err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9295,33 +10484,24 @@ pub fn fdClose(workspace: u32, fd: u32) -> Promise { }) } -// fd_flush +// greeter_device_in_progress_2_do_signify_trust #[allow(non_snake_case)] #[wasm_bindgen] -pub fn fdFlush(workspace: u32, fd: u32) -> Promise { +pub fn greeterDeviceInProgress2DoSignifyTrust(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let fd = { - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) - }?; - - let ret = libparsec::fd_flush(workspace, fd).await; + let ret = libparsec::greeter_device_in_progress_2_do_signify_trust(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - let _ = value; - JsValue::null() - }; + let js_value = struct_device_greet_in_progress3_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_fd_flush_error_rs_to_js(err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9329,30 +10509,25 @@ pub fn fdFlush(workspace: u32, fd: u32) -> Promise { }) } -// fd_read +// greeter_device_in_progress_3_do_get_claim_requests #[allow(non_snake_case)] #[wasm_bindgen] -pub fn fdRead(workspace: u32, fd: u32, offset: u64, size: u64) -> Promise { +pub fn greeterDeviceInProgress3DoGetClaimRequests(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let fd = { - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) - }?; - - let ret = libparsec::fd_read(workspace, fd, offset, size).await; + let ret = + libparsec::greeter_device_in_progress_3_do_get_claim_requests(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from(Uint8Array::from(value.as_ref())); + let js_value = struct_device_greet_in_progress4_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_fd_read_error_rs_to_js(err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9360,18 +10535,24 @@ pub fn fdRead(workspace: u32, fd: u32, offset: u64, size: u64) -> Promise { }) } -// fd_resize +// greeter_device_in_progress_4_do_create #[allow(non_snake_case)] #[wasm_bindgen] -pub fn fdResize(workspace: u32, fd: u32, length: u64, truncate_only: bool) -> Promise { +pub fn greeterDeviceInProgress4DoCreate( + canceller: u32, + handle: u32, + device_label: String, +) -> Promise { future_to_promise(async move { - let fd = { - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) + let device_label = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(device_label).map_err(|e| TypeError::new(e.as_ref())) }?; - - let ret = libparsec::fd_resize(workspace, fd, length, truncate_only).await; + let ret = + libparsec::greeter_device_in_progress_4_do_create(canceller, handle, device_label) + .await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -9386,7 +10567,7 @@ pub fn fdResize(workspace: u32, fd: u32, length: u64, truncate_only: bool) -> Pr Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_fd_resize_error_rs_to_js(err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9394,32 +10575,24 @@ pub fn fdResize(workspace: u32, fd: u32, length: u64, truncate_only: bool) -> Pr }) } -// fd_write +// greeter_device_initial_do_wait_peer #[allow(non_snake_case)] #[wasm_bindgen] -pub fn fdWrite(workspace: u32, fd: u32, offset: u64, data: Uint8Array) -> Promise { +pub fn greeterDeviceInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let fd = { - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) - }?; - - let data = data.to_vec(); - - let ret = libparsec::fd_write(workspace, fd, offset, &data.to_vec()).await; + let ret = libparsec::greeter_device_initial_do_wait_peer(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from(value); + let js_value = struct_device_greet_in_progress1_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_fd_write_error_rs_to_js(err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9427,32 +10600,24 @@ pub fn fdWrite(workspace: u32, fd: u32, offset: u64, data: Uint8Array) -> Promis }) } -// fd_write_constrained_io +// greeter_user_in_progress_1_do_wait_peer_trust #[allow(non_snake_case)] #[wasm_bindgen] -pub fn fdWriteConstrainedIo(workspace: u32, fd: u32, offset: u64, data: Uint8Array) -> Promise { +pub fn greeterUserInProgress1DoWaitPeerTrust(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let fd = { - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) - }?; - - let data = data.to_vec(); - - let ret = libparsec::fd_write_constrained_io(workspace, fd, offset, &data.to_vec()).await; + let ret = libparsec::greeter_user_in_progress_1_do_wait_peer_trust(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from(value); + let js_value = struct_user_greet_in_progress2_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_fd_write_error_rs_to_js(err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9460,32 +10625,27 @@ pub fn fdWriteConstrainedIo(workspace: u32, fd: u32, offset: u64, data: Uint8Arr }) } -// fd_write_start_eof +// greeter_user_in_progress_2_do_deny_trust #[allow(non_snake_case)] #[wasm_bindgen] -pub fn fdWriteStartEof(workspace: u32, fd: u32, data: Uint8Array) -> Promise { +pub fn greeterUserInProgress2DoDenyTrust(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let fd = { - let custom_from_rs_u32 = - |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; - custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) - }?; - - let data = data.to_vec(); - - let ret = libparsec::fd_write_start_eof(workspace, fd, &data.to_vec()).await; + let ret = libparsec::greeter_user_in_progress_2_do_deny_trust(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from(value); + let js_value = { + let _ = value; + JsValue::null() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_fd_write_error_rs_to_js(err)?; + let js_err = variant_greet_in_progress_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9493,91 +10653,43 @@ pub fn fdWriteStartEof(workspace: u32, fd: u32, data: Uint8Array) -> Promise { }) } -// get_default_config_dir -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn getDefaultConfigDir() -> Promise { - future_to_promise(async move { - let ret = libparsec::get_default_config_dir(); - Ok(JsValue::from_str({ - let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { - path.into_os_string() - .into_string() - .map_err(|_| "Path contains non-utf8 characters") - }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), - } - .as_ref() - })) - }) -} - -// get_default_data_base_dir +// greeter_user_in_progress_2_do_signify_trust #[allow(non_snake_case)] #[wasm_bindgen] -pub fn getDefaultDataBaseDir() -> Promise { +pub fn greeterUserInProgress2DoSignifyTrust(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::get_default_data_base_dir(); - Ok(JsValue::from_str({ - let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { - path.into_os_string() - .into_string() - .map_err(|_| "Path contains non-utf8 characters") - }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + let ret = libparsec::greeter_user_in_progress_2_do_signify_trust(canceller, handle).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = struct_user_greet_in_progress3_info_rs_to_js(value)?; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj } - .as_ref() - })) - }) -} - -// get_default_mountpoint_base_dir -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn getDefaultMountpointBaseDir() -> Promise { - future_to_promise(async move { - let ret = libparsec::get_default_mountpoint_base_dir(); - Ok(JsValue::from_str({ - let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { - path.into_os_string() - .into_string() - .map_err(|_| "Path contains non-utf8 characters") - }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_greet_in_progress_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj } - .as_ref() - })) - }) -} - -// get_platform -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn getPlatform() -> Promise { - future_to_promise(async move { - let ret = libparsec::get_platform(); - Ok(JsValue::from_str(enum_platform_rs_to_js(ret))) + }) }) } -// greeter_device_in_progress_1_do_wait_peer_trust +// greeter_user_in_progress_3_do_get_claim_requests #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterDeviceInProgress1DoWaitPeerTrust(canceller: u32, handle: u32) -> Promise { +pub fn greeterUserInProgress3DoGetClaimRequests(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { let ret = - libparsec::greeter_device_in_progress_1_do_wait_peer_trust(canceller, handle).await; + libparsec::greeter_user_in_progress_3_do_get_claim_requests(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_device_greet_in_progress2_info_rs_to_js(value)?; + let js_value = struct_user_greet_in_progress4_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } @@ -9592,12 +10704,36 @@ pub fn greeterDeviceInProgress1DoWaitPeerTrust(canceller: u32, handle: u32) -> P }) } -// greeter_device_in_progress_2_do_deny_trust +// greeter_user_in_progress_4_do_create #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterDeviceInProgress2DoDenyTrust(canceller: u32, handle: u32) -> Promise { +pub fn greeterUserInProgress4DoCreate( + canceller: u32, + handle: u32, + human_handle: Object, + device_label: String, + profile: String, +) -> Promise { future_to_promise(async move { - let ret = libparsec::greeter_device_in_progress_2_do_deny_trust(canceller, handle).await; + let human_handle = human_handle.into(); + let human_handle = struct_human_handle_js_to_rs(human_handle)?; + + let device_label = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(device_label).map_err(|e| TypeError::new(e.as_ref())) + }?; + let profile = enum_user_profile_js_to_rs(&profile)?; + + let ret = libparsec::greeter_user_in_progress_4_do_create( + canceller, + handle, + human_handle, + device_label, + profile, + ) + .await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -9620,17 +10756,17 @@ pub fn greeterDeviceInProgress2DoDenyTrust(canceller: u32, handle: u32) -> Promi }) } -// greeter_device_in_progress_2_do_signify_trust +// greeter_user_initial_do_wait_peer #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterDeviceInProgress2DoSignifyTrust(canceller: u32, handle: u32) -> Promise { +pub fn greeterUserInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::greeter_device_in_progress_2_do_signify_trust(canceller, handle).await; + let ret = libparsec::greeter_user_initial_do_wait_peer(canceller, handle).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_device_greet_in_progress3_info_rs_to_js(value)?; + let js_value = struct_user_greet_in_progress1_info_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } @@ -9645,25 +10781,75 @@ pub fn greeterDeviceInProgress2DoSignifyTrust(canceller: u32, handle: u32) -> Pr }) } -// greeter_device_in_progress_3_do_get_claim_requests +// is_keyring_available #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterDeviceInProgress3DoGetClaimRequests(canceller: u32, handle: u32) -> Promise { +pub fn isKeyringAvailable() -> Promise { future_to_promise(async move { - let ret = - libparsec::greeter_device_in_progress_3_do_get_claim_requests(canceller, handle).await; + let ret = libparsec::is_keyring_available(); + Ok(ret.into()) + }) +} + +// list_available_devices +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn listAvailableDevices(path: String) -> Promise { + future_to_promise(async move { + let path = { + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + }?; + + let ret = libparsec::list_available_devices(&path).await; + Ok({ + // Array::new_with_length allocates with `undefined` value, that's why we `set` value + let js_array = Array::new_with_length(ret.len() as u32); + for (i, elem) in ret.into_iter().enumerate() { + let js_elem = struct_available_device_rs_to_js(elem)?; + js_array.set(i as u32, js_elem); + } + js_array.into() + }) + }) +} + +// mountpoint_to_os_path +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn mountpointToOsPath(mountpoint: u32, parsec_path: String) -> Promise { + future_to_promise(async move { + let parsec_path = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(parsec_path).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::mountpoint_to_os_path(mountpoint, parsec_path).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_device_greet_in_progress4_info_rs_to_js(value)?; + let js_value = JsValue::from_str({ + let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { + path.into_os_string() + .into_string() + .map_err(|_| "Path contains non-utf8 characters") + }; + match custom_to_rs_string(value) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_greet_in_progress_error_rs_to_js(err)?; + let js_err = variant_mountpoint_to_os_path_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9671,24 +10857,12 @@ pub fn greeterDeviceInProgress3DoGetClaimRequests(canceller: u32, handle: u32) - }) } -// greeter_device_in_progress_4_do_create +// mountpoint_unmount #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterDeviceInProgress4DoCreate( - canceller: u32, - handle: u32, - device_label: String, -) -> Promise { +pub fn mountpointUnmount(mountpoint: u32) -> Promise { future_to_promise(async move { - let device_label = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(device_label).map_err(|e| TypeError::new(e.as_ref())) - }?; - let ret = - libparsec::greeter_device_in_progress_4_do_create(canceller, handle, device_label) - .await; + let ret = libparsec::mountpoint_unmount(mountpoint).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -9703,7 +10877,7 @@ pub fn greeterDeviceInProgress4DoCreate( Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_greet_in_progress_error_rs_to_js(err)?; + let js_err = variant_mountpoint_unmount_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9711,24 +10885,34 @@ pub fn greeterDeviceInProgress4DoCreate( }) } -// greeter_device_initial_do_wait_peer +// new_canceller #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterDeviceInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { +pub fn newCanceller() -> Promise { future_to_promise(async move { - let ret = libparsec::greeter_device_initial_do_wait_peer(canceller, handle).await; + let ret = libparsec::new_canceller(); + Ok(JsValue::from(ret)) + }) +} + +// parse_parsec_addr +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn parseParsecAddr(url: String) -> Promise { + future_to_promise(async move { + let ret = libparsec::parse_parsec_addr(&url); Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_device_greet_in_progress1_info_rs_to_js(value)?; + let js_value = variant_parsed_parsec_addr_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_greet_in_progress_error_rs_to_js(err)?; + let js_err = variant_parse_parsec_addr_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9736,103 +10920,154 @@ pub fn greeterDeviceInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { }) } -// greeter_user_in_progress_1_do_wait_peer_trust +// path_filename #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterUserInProgress1DoWaitPeerTrust(canceller: u32, handle: u32) -> Promise { +pub fn pathFilename(path: String) -> Promise { future_to_promise(async move { - let ret = libparsec::greeter_user_in_progress_1_do_wait_peer_trust(canceller, handle).await; + let path = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::path_filename(&path); Ok(match ret { - Ok(value) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_user_greet_in_progress2_info_rs_to_js(value)?; - Reflect::set(&js_obj, &"value".into(), &js_value)?; - js_obj - } - Err(err) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_greet_in_progress_error_rs_to_js(err)?; - Reflect::set(&js_obj, &"error".into(), &js_err)?; - js_obj - } + Some(val) => JsValue::from_str(val.as_ref()), + None => JsValue::NULL, }) }) } -// greeter_user_in_progress_2_do_deny_trust +// path_join #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterUserInProgress2DoDenyTrust(canceller: u32, handle: u32) -> Promise { +pub fn pathJoin(parent: String, child: String) -> Promise { future_to_promise(async move { - let ret = libparsec::greeter_user_in_progress_2_do_deny_trust(canceller, handle).await; - Ok(match ret { - Ok(value) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - let _ = value; - JsValue::null() - }; - Reflect::set(&js_obj, &"value".into(), &js_value)?; - js_obj + let parent = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(parent).map_err(|e| TypeError::new(e.as_ref())) + }?; + let child = { + let custom_from_rs_string = |s: String| -> Result<_, _> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(child).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::path_join(&parent, &child); + Ok(JsValue::from_str({ + let custom_to_rs_string = + |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), } - Err(err) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_greet_in_progress_error_rs_to_js(err)?; - Reflect::set(&js_obj, &"error".into(), &js_err)?; - js_obj + .as_ref() + })) + }) +} + +// path_normalize +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn pathNormalize(path: String) -> Promise { + future_to_promise(async move { + let path = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::path_normalize(path); + Ok(JsValue::from_str({ + let custom_to_rs_string = + |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), } - }) + .as_ref() + })) }) } -// greeter_user_in_progress_2_do_signify_trust +// path_parent #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterUserInProgress2DoSignifyTrust(canceller: u32, handle: u32) -> Promise { +pub fn pathParent(path: String) -> Promise { future_to_promise(async move { - let ret = libparsec::greeter_user_in_progress_2_do_signify_trust(canceller, handle).await; - Ok(match ret { - Ok(value) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_user_greet_in_progress3_info_rs_to_js(value)?; - Reflect::set(&js_obj, &"value".into(), &js_value)?; - js_obj + let path = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::path_parent(&path); + Ok(JsValue::from_str({ + let custom_to_rs_string = + |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; + match custom_to_rs_string(ret) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), } - Err(err) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_greet_in_progress_error_rs_to_js(err)?; - Reflect::set(&js_obj, &"error".into(), &js_err)?; - js_obj + .as_ref() + })) + }) +} + +// path_split +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn pathSplit(path: String) -> Promise { + future_to_promise(async move { + let path = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::path_split(&path); + Ok({ + // Array::new_with_length allocates with `undefined` value, that's why we `set` value + let js_array = Array::new_with_length(ret.len() as u32); + for (i, elem) in ret.into_iter().enumerate() { + let js_elem = JsValue::from_str(elem.as_ref()); + js_array.set(i as u32, js_elem); } + js_array.into() }) }) } -// greeter_user_in_progress_3_do_get_claim_requests +// test_drop_testbed #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterUserInProgress3DoGetClaimRequests(canceller: u32, handle: u32) -> Promise { +pub fn testDropTestbed(path: String) -> Promise { future_to_promise(async move { - let ret = - libparsec::greeter_user_in_progress_3_do_get_claim_requests(canceller, handle).await; + let path = { + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + }?; + + let ret = libparsec::test_drop_testbed(&path).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_user_greet_in_progress4_info_rs_to_js(value)?; + let js_value = { + let _ = value; + JsValue::null() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_greet_in_progress_error_rs_to_js(err)?; + let js_err = variant_testbed_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9840,43 +11075,31 @@ pub fn greeterUserInProgress3DoGetClaimRequests(canceller: u32, handle: u32) -> }) } -// greeter_user_in_progress_4_do_create +// test_get_testbed_bootstrap_organization_addr #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterUserInProgress4DoCreate( - canceller: u32, - handle: u32, - human_handle: Object, - device_label: String, - profile: String, -) -> Promise { +pub fn testGetTestbedBootstrapOrganizationAddr(discriminant_dir: String) -> Promise { future_to_promise(async move { - let human_handle = human_handle.into(); - let human_handle = struct_human_handle_js_to_rs(human_handle)?; - - let device_label = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::DeviceLabel::try_from(s.as_str()).map_err(|e| e.to_string()) - }; - custom_from_rs_string(device_label).map_err(|e| TypeError::new(e.as_ref())) - }?; - let profile = enum_user_profile_js_to_rs(&profile)?; - - let ret = libparsec::greeter_user_in_progress_4_do_create( - canceller, - handle, - human_handle, - device_label, - profile, - ) - .await; + let discriminant_dir = { + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + custom_from_rs_string(discriminant_dir).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::test_get_testbed_bootstrap_organization_addr(&discriminant_dir); Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - let _ = value; - JsValue::null() + let js_value = match value { + Some(val) => JsValue::from_str({ + let custom_to_rs_string = |addr: libparsec::ParsecOrganizationBootstrapAddr| -> Result { Ok(addr.to_url().into()) }; + match custom_to_rs_string(val) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }), + None => JsValue::NULL, }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj @@ -9884,7 +11107,7 @@ pub fn greeterUserInProgress4DoCreate( Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_greet_in_progress_error_rs_to_js(err)?; + let js_err = variant_testbed_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9892,24 +11115,32 @@ pub fn greeterUserInProgress4DoCreate( }) } -// greeter_user_initial_do_wait_peer +// test_get_testbed_organization_id #[allow(non_snake_case)] #[wasm_bindgen] -pub fn greeterUserInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { +pub fn testGetTestbedOrganizationId(discriminant_dir: String) -> Promise { future_to_promise(async move { - let ret = libparsec::greeter_user_initial_do_wait_peer(canceller, handle).await; + let discriminant_dir = { + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + custom_from_rs_string(discriminant_dir).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::test_get_testbed_organization_id(&discriminant_dir); Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = struct_user_greet_in_progress1_info_rs_to_js(value)?; + let js_value = match value { + Some(val) => JsValue::from_str(val.as_ref()), + None => JsValue::NULL, + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_greet_in_progress_error_rs_to_js(err)?; + let js_err = variant_testbed_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9917,52 +11148,26 @@ pub fn greeterUserInitialDoWaitPeer(canceller: u32, handle: u32) -> Promise { }) } -// is_keyring_available -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn isKeyringAvailable() -> Promise { - future_to_promise(async move { - let ret = libparsec::is_keyring_available(); - Ok(ret.into()) - }) -} - -// list_available_devices +// test_new_testbed #[allow(non_snake_case)] #[wasm_bindgen] -pub fn listAvailableDevices(path: String) -> Promise { +pub fn testNewTestbed(template: String, test_server: Option) -> Promise { future_to_promise(async move { - let path = { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; - custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) - }?; + let test_server = match test_server { + Some(test_server) => { + let test_server = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::ParsecAddr::from_any(&s).map_err(|e| e.to_string()) + }; + custom_from_rs_string(test_server).map_err(|e| TypeError::new(e.as_ref())) + }?; - let ret = libparsec::list_available_devices(&path).await; - Ok({ - // Array::new_with_length allocates with `undefined` value, that's why we `set` value - let js_array = Array::new_with_length(ret.len() as u32); - for (i, elem) in ret.into_iter().enumerate() { - let js_elem = struct_available_device_rs_to_js(elem)?; - js_array.set(i as u32, js_elem); + Some(test_server) } - js_array.into() - }) - }) -} + None => None, + }; -// mountpoint_to_os_path -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn mountpointToOsPath(mountpoint: u32, parsec_path: String) -> Promise { - future_to_promise(async move { - let parsec_path = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) - }; - custom_from_rs_string(parsec_path).map_err(|e| TypeError::new(e.as_ref())) - }?; - let ret = libparsec::mountpoint_to_os_path(mountpoint, parsec_path).await; + let ret = libparsec::test_new_testbed(&template, test_server.as_ref()).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); @@ -9985,7 +11190,7 @@ pub fn mountpointToOsPath(mountpoint: u32, parsec_path: String) -> Promise { Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_mountpoint_to_os_path_error_rs_to_js(err)?; + let js_err = variant_testbed_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -9993,146 +11198,120 @@ pub fn mountpointToOsPath(mountpoint: u32, parsec_path: String) -> Promise { }) } -// mountpoint_unmount +// validate_device_label #[allow(non_snake_case)] #[wasm_bindgen] -pub fn mountpointUnmount(mountpoint: u32) -> Promise { +pub fn validateDeviceLabel(raw: String) -> Promise { future_to_promise(async move { - let ret = libparsec::mountpoint_unmount(mountpoint).await; - Ok(match ret { - Ok(value) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - let _ = value; - JsValue::null() - }; - Reflect::set(&js_obj, &"value".into(), &js_value)?; - js_obj - } - Err(err) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_mountpoint_unmount_error_rs_to_js(err)?; - Reflect::set(&js_obj, &"error".into(), &js_err)?; - js_obj - } - }) + let ret = libparsec::validate_device_label(&raw); + Ok(ret.into()) }) } -// new_canceller +// validate_email #[allow(non_snake_case)] #[wasm_bindgen] -pub fn newCanceller() -> Promise { +pub fn validateEmail(raw: String) -> Promise { future_to_promise(async move { - let ret = libparsec::new_canceller(); - Ok(JsValue::from(ret)) + let ret = libparsec::validate_email(&raw); + Ok(ret.into()) }) } -// parse_parsec_addr +// validate_entry_name #[allow(non_snake_case)] #[wasm_bindgen] -pub fn parseParsecAddr(url: String) -> Promise { +pub fn validateEntryName(raw: String) -> Promise { future_to_promise(async move { - let ret = libparsec::parse_parsec_addr(&url); - Ok(match ret { - Ok(value) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = variant_parsed_parsec_addr_rs_to_js(value)?; - Reflect::set(&js_obj, &"value".into(), &js_value)?; - js_obj - } - Err(err) => { - let js_obj = Object::new().into(); - Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_parse_parsec_addr_error_rs_to_js(err)?; - Reflect::set(&js_obj, &"error".into(), &js_err)?; - js_obj - } - }) + let ret = libparsec::validate_entry_name(&raw); + Ok(ret.into()) }) } -// path_filename +// validate_human_handle_label #[allow(non_snake_case)] #[wasm_bindgen] -pub fn pathFilename(path: String) -> Promise { +pub fn validateHumanHandleLabel(raw: String) -> Promise { future_to_promise(async move { - let path = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) - }; - custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) - }?; - let ret = libparsec::path_filename(&path); - Ok(match ret { - Some(val) => JsValue::from_str(val.as_ref()), - None => JsValue::NULL, - }) + let ret = libparsec::validate_human_handle_label(&raw); + Ok(ret.into()) }) } -// path_join +// validate_invitation_token #[allow(non_snake_case)] #[wasm_bindgen] -pub fn pathJoin(parent: String, child: String) -> Promise { +pub fn validateInvitationToken(raw: String) -> Promise { future_to_promise(async move { - let parent = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) - }; - custom_from_rs_string(parent).map_err(|e| TypeError::new(e.as_ref())) - }?; - let child = { - let custom_from_rs_string = |s: String| -> Result<_, _> { - s.parse::().map_err(|e| e.to_string()) - }; - custom_from_rs_string(child).map_err(|e| TypeError::new(e.as_ref())) - }?; - let ret = libparsec::path_join(&parent, &child); - Ok(JsValue::from_str({ - let custom_to_rs_string = - |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), - } - .as_ref() - })) + let ret = libparsec::validate_invitation_token(&raw); + Ok(ret.into()) }) } -// path_normalize +// validate_organization_id #[allow(non_snake_case)] #[wasm_bindgen] -pub fn pathNormalize(path: String) -> Promise { +pub fn validateOrganizationId(raw: String) -> Promise { future_to_promise(async move { - let path = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) + let ret = libparsec::validate_organization_id(&raw); + Ok(ret.into()) + }) +} + +// validate_path +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn validatePath(raw: String) -> Promise { + future_to_promise(async move { + let ret = libparsec::validate_path(&raw); + Ok(ret.into()) + }) +} + +// wait_for_device_available +#[allow(non_snake_case)] +#[wasm_bindgen] +pub fn waitForDeviceAvailable(config_dir: String, device_id: String) -> Promise { + future_to_promise(async move { + let config_dir = { + let custom_from_rs_string = + |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; + custom_from_rs_string(config_dir).map_err(|e| TypeError::new(e.as_ref())) + }?; + + let device_id = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::DeviceID::from_hex(s.as_str()).map_err(|e| e.to_string()) }; - custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_string(device_id).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::path_normalize(path); - Ok(JsValue::from_str({ - let custom_to_rs_string = - |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + let ret = libparsec::wait_for_device_available(&config_dir, device_id).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = { + let _ = value; + JsValue::null() + }; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_wait_for_device_available_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj } - .as_ref() - })) + }) }) } -// path_parent +// workspace_create_file #[allow(non_snake_case)] #[wasm_bindgen] -pub fn pathParent(path: String) -> Promise { +pub fn workspaceCreateFile(workspace: u32, path: String) -> Promise { future_to_promise(async move { let path = { let custom_from_rs_string = |s: String| -> Result<_, String> { @@ -10140,23 +11319,38 @@ pub fn pathParent(path: String) -> Promise { }; custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::path_parent(&path); - Ok(JsValue::from_str({ - let custom_to_rs_string = - |path: libparsec::FsPath| -> Result<_, &'static str> { Ok(path.to_string()) }; - match custom_to_rs_string(ret) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + let ret = libparsec::workspace_create_file(workspace, path).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = JsValue::from_str({ + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(value) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj } - .as_ref() - })) + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_workspace_create_file_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) }) } -// path_split +// workspace_create_folder #[allow(non_snake_case)] #[wasm_bindgen] -pub fn pathSplit(path: String) -> Promise { +pub fn workspaceCreateFolder(workspace: u32, path: String) -> Promise { future_to_promise(async move { let path = { let custom_from_rs_string = |s: String| -> Result<_, String> { @@ -10164,46 +11358,27 @@ pub fn pathSplit(path: String) -> Promise { }; custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::path_split(&path); - Ok({ - // Array::new_with_length allocates with `undefined` value, that's why we `set` value - let js_array = Array::new_with_length(ret.len() as u32); - for (i, elem) in ret.into_iter().enumerate() { - let js_elem = JsValue::from_str(elem.as_ref()); - js_array.set(i as u32, js_elem); - } - js_array.into() - }) - }) -} - -// test_drop_testbed -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn testDropTestbed(path: String) -> Promise { - future_to_promise(async move { - let path = { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; - custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) - }?; - - let ret = libparsec::test_drop_testbed(&path).await; + let ret = libparsec::workspace_create_folder(workspace, path).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = { - let _ = value; - JsValue::null() - }; + let js_value = JsValue::from_str({ + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(value) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_testbed_error_rs_to_js(err)?; + let js_err = variant_workspace_create_folder_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -10211,39 +11386,38 @@ pub fn testDropTestbed(path: String) -> Promise { }) } -// test_get_testbed_bootstrap_organization_addr +// workspace_create_folder_all #[allow(non_snake_case)] #[wasm_bindgen] -pub fn testGetTestbedBootstrapOrganizationAddr(discriminant_dir: String) -> Promise { +pub fn workspaceCreateFolderAll(workspace: u32, path: String) -> Promise { future_to_promise(async move { - let discriminant_dir = { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; - custom_from_rs_string(discriminant_dir).map_err(|e| TypeError::new(e.as_ref())) + let path = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::test_get_testbed_bootstrap_organization_addr(&discriminant_dir); + let ret = libparsec::workspace_create_folder_all(workspace, path).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = match value { - Some(val) => JsValue::from_str({ - let custom_to_rs_string = |addr: libparsec::ParsecOrganizationBootstrapAddr| -> Result { Ok(addr.to_url().into()) }; - match custom_to_rs_string(val) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), - } - .as_ref() - }), - None => JsValue::NULL, - }; + let js_value = JsValue::from_str({ + let custom_to_rs_string = + |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; + match custom_to_rs_string(value) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_testbed_error_rs_to_js(err)?; + let js_err = variant_workspace_create_folder_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -10251,32 +11425,40 @@ pub fn testGetTestbedBootstrapOrganizationAddr(discriminant_dir: String) -> Prom }) } -// test_get_testbed_organization_id +// workspace_decrypt_path_addr #[allow(non_snake_case)] #[wasm_bindgen] -pub fn testGetTestbedOrganizationId(discriminant_dir: String) -> Promise { +pub fn workspaceDecryptPathAddr(workspace: u32, link: String) -> Promise { future_to_promise(async move { - let discriminant_dir = { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; - custom_from_rs_string(discriminant_dir).map_err(|e| TypeError::new(e.as_ref())) + let link = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + libparsec::ParsecWorkspacePathAddr::from_any(&s).map_err(|e| e.to_string()) + }; + custom_from_rs_string(link).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::test_get_testbed_organization_id(&discriminant_dir); + + let ret = libparsec::workspace_decrypt_path_addr(workspace, &link).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = match value { - Some(val) => JsValue::from_str(val.as_ref()), - None => JsValue::NULL, - }; + let js_value = JsValue::from_str({ + let custom_to_rs_string = |path: libparsec::FsPath| -> Result<_, &'static str> { + Ok(path.to_string()) + }; + match custom_to_rs_string(value) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + } + .as_ref() + }); Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_testbed_error_rs_to_js(err)?; + let js_err = variant_workspace_decrypt_path_addr_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -10284,36 +11466,28 @@ pub fn testGetTestbedOrganizationId(discriminant_dir: String) -> Promise { }) } -// test_new_testbed +// workspace_generate_path_addr #[allow(non_snake_case)] #[wasm_bindgen] -pub fn testNewTestbed(template: String, test_server: Option) -> Promise { +pub fn workspaceGeneratePathAddr(workspace: u32, path: String) -> Promise { future_to_promise(async move { - let test_server = match test_server { - Some(test_server) => { - let test_server = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::ParsecAddr::from_any(&s).map_err(|e| e.to_string()) - }; - custom_from_rs_string(test_server).map_err(|e| TypeError::new(e.as_ref())) - }?; - - Some(test_server) - } - None => None, - }; + let path = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) + }; + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + }?; - let ret = libparsec::test_new_testbed(&template, test_server.as_ref()).await; + let ret = libparsec::workspace_generate_path_addr(workspace, &path).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; let js_value = JsValue::from_str({ - let custom_to_rs_string = |path: std::path::PathBuf| -> Result<_, _> { - path.into_os_string() - .into_string() - .map_err(|_| "Path contains non-utf8 characters") - }; + let custom_to_rs_string = + |addr: libparsec::ParsecWorkspacePathAddr| -> Result { + Ok(addr.to_url().into()) + }; match custom_to_rs_string(value) { Ok(ok) => ok, Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), @@ -10326,7 +11500,7 @@ pub fn testNewTestbed(template: String, test_server: Option) -> Promise Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_testbed_error_rs_to_js(err)?; + let js_err = variant_workspace_generate_path_addr_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -10334,101 +11508,174 @@ pub fn testNewTestbed(template: String, test_server: Option) -> Promise }) } -// validate_device_label -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn validateDeviceLabel(raw: String) -> Promise { - future_to_promise(async move { - let ret = libparsec::validate_device_label(&raw); - Ok(ret.into()) - }) -} - -// validate_email +// workspace_history_fd_close #[allow(non_snake_case)] #[wasm_bindgen] -pub fn validateEmail(raw: String) -> Promise { +pub fn workspaceHistoryFdClose(workspace: u32, fd: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::validate_email(&raw); - Ok(ret.into()) + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::workspace_history_fd_close(workspace, fd); + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = { + let _ = value; + JsValue::null() + }; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_workspace_history_fd_close_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) }) } -// validate_entry_name +// workspace_history_fd_read #[allow(non_snake_case)] #[wasm_bindgen] -pub fn validateEntryName(raw: String) -> Promise { +pub fn workspaceHistoryFdRead(workspace: u32, fd: u32, offset: u64, size: u64) -> Promise { future_to_promise(async move { - let ret = libparsec::validate_entry_name(&raw); - Ok(ret.into()) - }) -} + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) + }?; -// validate_human_handle_label -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn validateHumanHandleLabel(raw: String) -> Promise { - future_to_promise(async move { - let ret = libparsec::validate_human_handle_label(&raw); - Ok(ret.into()) + let ret = libparsec::workspace_history_fd_read(workspace, fd, offset, size).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = JsValue::from(Uint8Array::from(value.as_ref())); + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_workspace_history_fd_read_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) }) } -// validate_invitation_token +// workspace_history_fd_stat #[allow(non_snake_case)] #[wasm_bindgen] -pub fn validateInvitationToken(raw: String) -> Promise { +pub fn workspaceHistoryFdStat(workspace: u32, fd: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::validate_invitation_token(&raw); - Ok(ret.into()) - }) -} + let fd = { + let custom_from_rs_u32 = + |raw: u32| -> Result<_, String> { Ok(libparsec::FileDescriptor(raw)) }; + custom_from_rs_u32(fd).map_err(|e| TypeError::new(e.as_ref())) + }?; -// validate_organization_id -#[allow(non_snake_case)] -#[wasm_bindgen] -pub fn validateOrganizationId(raw: String) -> Promise { - future_to_promise(async move { - let ret = libparsec::validate_organization_id(&raw); - Ok(ret.into()) + let ret = libparsec::workspace_history_fd_stat(workspace, fd).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = struct_workspace_history_file_stat_rs_to_js(value)?; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = variant_workspace_history_fd_stat_error_rs_to_js(err)?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) }) } -// validate_path +// workspace_history_get_workspace_manifest_v1_timestamp #[allow(non_snake_case)] #[wasm_bindgen] -pub fn validatePath(raw: String) -> Promise { +pub fn workspaceHistoryGetWorkspaceManifestV1Timestamp(workspace: u32) -> Promise { future_to_promise(async move { - let ret = libparsec::validate_path(&raw); - Ok(ret.into()) + let ret = libparsec::workspace_history_get_workspace_manifest_v1_timestamp(workspace).await; + Ok(match ret { + Ok(value) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &true.into())?; + let js_value = match value { + Some(val) => { + let custom_to_rs_f64 = + |dt: libparsec::DateTime| -> Result { + Ok((dt.as_timestamp_micros() as f64) / 1_000_000f64) + }; + let v = match custom_to_rs_f64(val) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + }; + JsValue::from(v) + } + None => JsValue::NULL, + }; + Reflect::set(&js_obj, &"value".into(), &js_value)?; + js_obj + } + Err(err) => { + let js_obj = Object::new().into(); + Reflect::set(&js_obj, &"ok".into(), &false.into())?; + let js_err = + variant_workspace_history_get_workspace_manifest_v1_timestamp_error_rs_to_js( + err, + )?; + Reflect::set(&js_obj, &"error".into(), &js_err)?; + js_obj + } + }) }) } -// wait_for_device_available +// workspace_history_open_file #[allow(non_snake_case)] #[wasm_bindgen] -pub fn waitForDeviceAvailable(config_dir: String, device_id: String) -> Promise { +pub fn workspaceHistoryOpenFile(workspace: u32, at: f64, path: String) -> Promise { future_to_promise(async move { - let config_dir = { - let custom_from_rs_string = - |s: String| -> Result<_, &'static str> { Ok(std::path::PathBuf::from(s)) }; - custom_from_rs_string(config_dir).map_err(|e| TypeError::new(e.as_ref())) + let at = { + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + custom_from_rs_f64(at).map_err(|e| TypeError::new(e.as_ref())) }?; - let device_id = { - let custom_from_rs_string = |s: String| -> Result { - libparsec::DeviceID::from_hex(s.as_str()).map_err(|e| e.to_string()) + let path = { + let custom_from_rs_string = |s: String| -> Result<_, String> { + s.parse::().map_err(|e| e.to_string()) }; - custom_from_rs_string(device_id).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::wait_for_device_available(&config_dir, device_id).await; + let ret = libparsec::workspace_history_open_file(workspace, at, path).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; let js_value = { - let _ = value; - JsValue::null() + let custom_to_rs_u32 = + |fd: libparsec::FileDescriptor| -> Result<_, &'static str> { Ok(fd.0) }; + let v = match custom_to_rs_u32(value) { + Ok(ok) => ok, + Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + }; + JsValue::from(v) }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj @@ -10436,7 +11683,7 @@ pub fn waitForDeviceAvailable(config_dir: String, device_id: String) -> Promise Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_wait_for_device_available_error_rs_to_js(err)?; + let js_err = variant_workspace_history_open_file_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -10444,38 +11691,46 @@ pub fn waitForDeviceAvailable(config_dir: String, device_id: String) -> Promise }) } -// workspace_create_file +// workspace_history_open_file_by_id #[allow(non_snake_case)] #[wasm_bindgen] -pub fn workspaceCreateFile(workspace: u32, path: String) -> Promise { +pub fn workspaceHistoryOpenFileById(workspace: u32, at: f64, entry_id: String) -> Promise { future_to_promise(async move { - let path = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) + let at = { + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") }; - custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_f64(at).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::workspace_create_file(workspace, path).await; + + let entry_id = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(entry_id).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::workspace_history_open_file_by_id(workspace, at, entry_id).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from_str({ - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; - match custom_to_rs_string(value) { + let js_value = { + let custom_to_rs_u32 = + |fd: libparsec::FileDescriptor| -> Result<_, &'static str> { Ok(fd.0) }; + let v = match custom_to_rs_u32(value) { Ok(ok) => ok, Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), - } - .as_ref() - }); + }; + JsValue::from(v) + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_create_file_error_rs_to_js(err)?; + let js_err = variant_workspace_history_open_file_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -10483,38 +11738,39 @@ pub fn workspaceCreateFile(workspace: u32, path: String) -> Promise { }) } -// workspace_create_folder +// workspace_history_stat_entry #[allow(non_snake_case)] #[wasm_bindgen] -pub fn workspaceCreateFolder(workspace: u32, path: String) -> Promise { +pub fn workspaceHistoryStatEntry(workspace: u32, at: f64, path: String) -> Promise { future_to_promise(async move { + let at = { + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + custom_from_rs_f64(at).map_err(|e| TypeError::new(e.as_ref())) + }?; + let path = { let custom_from_rs_string = |s: String| -> Result<_, String> { s.parse::().map_err(|e| e.to_string()) }; custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::workspace_create_folder(workspace, path).await; + + let ret = libparsec::workspace_history_stat_entry(workspace, at, &path).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from_str({ - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; - match custom_to_rs_string(value) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), - } - .as_ref() - }); + let js_value = variant_workspace_history_entry_stat_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_create_folder_error_rs_to_js(err)?; + let js_err = variant_workspace_history_stat_entry_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -10522,38 +11778,38 @@ pub fn workspaceCreateFolder(workspace: u32, path: String) -> Promise { }) } -// workspace_create_folder_all +// workspace_history_stat_entry_by_id #[allow(non_snake_case)] #[wasm_bindgen] -pub fn workspaceCreateFolderAll(workspace: u32, path: String) -> Promise { +pub fn workspaceHistoryStatEntryById(workspace: u32, at: f64, entry_id: String) -> Promise { future_to_promise(async move { - let path = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) + let at = { + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") }; - custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_f64(at).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::workspace_create_folder_all(workspace, path).await; + + let entry_id = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(entry_id).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = libparsec::workspace_history_stat_entry_by_id(workspace, at, entry_id).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from_str({ - let custom_to_rs_string = - |x: libparsec::VlobID| -> Result { Ok(x.hex()) }; - match custom_to_rs_string(value) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), - } - .as_ref() - }); + let js_value = variant_workspace_history_entry_stat_rs_to_js(value)?; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_create_folder_error_rs_to_js(err)?; + let js_err = variant_workspace_history_stat_entry_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -10561,40 +11817,55 @@ pub fn workspaceCreateFolderAll(workspace: u32, path: String) -> Promise { }) } -// workspace_decrypt_path_addr +// workspace_history_stat_folder_children #[allow(non_snake_case)] #[wasm_bindgen] -pub fn workspaceDecryptPathAddr(workspace: u32, link: String) -> Promise { +pub fn workspaceHistoryStatFolderChildren(workspace: u32, at: f64, path: String) -> Promise { future_to_promise(async move { - let link = { + let at = { + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") + }; + custom_from_rs_f64(at).map_err(|e| TypeError::new(e.as_ref())) + }?; + + let path = { let custom_from_rs_string = |s: String| -> Result<_, String> { - libparsec::ParsecWorkspacePathAddr::from_any(&s).map_err(|e| e.to_string()) + s.parse::().map_err(|e| e.to_string()) }; - custom_from_rs_string(link).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::workspace_decrypt_path_addr(workspace, &link).await; + let ret = libparsec::workspace_history_stat_folder_children(workspace, at, &path).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from_str({ - let custom_to_rs_string = |path: libparsec::FsPath| -> Result<_, &'static str> { - Ok(path.to_string()) - }; - match custom_to_rs_string(value) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + let js_value = { + // Array::new_with_length allocates with `undefined` value, that's why we `set` value + let js_array = Array::new_with_length(value.len() as u32); + for (i, elem) in value.into_iter().enumerate() { + let js_elem = { + let (x1, x2) = elem; + let js_array = Array::new_with_length(2); + let js_value = JsValue::from_str(x1.as_ref()); + js_array.push(&js_value); + let js_value = variant_workspace_history_entry_stat_rs_to_js(x2)?; + js_array.push(&js_value); + js_array.into() + }; + js_array.set(i as u32, js_elem); } - .as_ref() - }); + js_array.into() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_decrypt_path_addr_error_rs_to_js(err)?; + let js_err = variant_workspace_history_stat_folder_children_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } @@ -10602,41 +11873,59 @@ pub fn workspaceDecryptPathAddr(workspace: u32, link: String) -> Promise { }) } -// workspace_generate_path_addr +// workspace_history_stat_folder_children_by_id #[allow(non_snake_case)] #[wasm_bindgen] -pub fn workspaceGeneratePathAddr(workspace: u32, path: String) -> Promise { +pub fn workspaceHistoryStatFolderChildrenById( + workspace: u32, + at: f64, + entry_id: String, +) -> Promise { future_to_promise(async move { - let path = { - let custom_from_rs_string = |s: String| -> Result<_, String> { - s.parse::().map_err(|e| e.to_string()) + let at = { + let custom_from_rs_f64 = |n: f64| -> Result<_, &'static str> { + libparsec::DateTime::from_timestamp_micros((n * 1_000_000f64) as i64) + .map_err(|_| "Out-of-bound datetime") }; - custom_from_rs_string(path).map_err(|e| TypeError::new(e.as_ref())) + custom_from_rs_f64(at).map_err(|e| TypeError::new(e.as_ref())) }?; - let ret = libparsec::workspace_generate_path_addr(workspace, &path).await; + let entry_id = { + let custom_from_rs_string = |s: String| -> Result { + libparsec::VlobID::from_hex(s.as_str()).map_err(|e| e.to_string()) + }; + custom_from_rs_string(entry_id).map_err(|e| TypeError::new(e.as_ref())) + }?; + let ret = + libparsec::workspace_history_stat_folder_children_by_id(workspace, at, entry_id).await; Ok(match ret { Ok(value) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &true.into())?; - let js_value = JsValue::from_str({ - let custom_to_rs_string = - |addr: libparsec::ParsecWorkspacePathAddr| -> Result { - Ok(addr.to_url().into()) + let js_value = { + // Array::new_with_length allocates with `undefined` value, that's why we `set` value + let js_array = Array::new_with_length(value.len() as u32); + for (i, elem) in value.into_iter().enumerate() { + let js_elem = { + let (x1, x2) = elem; + let js_array = Array::new_with_length(2); + let js_value = JsValue::from_str(x1.as_ref()); + js_array.push(&js_value); + let js_value = variant_workspace_history_entry_stat_rs_to_js(x2)?; + js_array.push(&js_value); + js_array.into() }; - match custom_to_rs_string(value) { - Ok(ok) => ok, - Err(err) => return Err(JsValue::from(TypeError::new(err.as_ref()))), + js_array.set(i as u32, js_elem); } - .as_ref() - }); + js_array.into() + }; Reflect::set(&js_obj, &"value".into(), &js_value)?; js_obj } Err(err) => { let js_obj = Object::new().into(); Reflect::set(&js_obj, &"ok".into(), &false.into())?; - let js_err = variant_workspace_generate_path_addr_error_rs_to_js(err)?; + let js_err = variant_workspace_history_stat_folder_children_error_rs_to_js(err)?; Reflect::set(&js_obj, &"error".into(), &js_err)?; js_obj } diff --git a/client/src/plugins/libparsec/definitions.ts b/client/src/plugins/libparsec/definitions.ts index b3a867cf459..9584f2cd1dd 100644 --- a/client/src/plugins/libparsec/definitions.ts +++ b/client/src/plugins/libparsec/definitions.ts @@ -267,6 +267,14 @@ export interface UserInfo { revokedBy: DeviceID | null } +export interface WorkspaceHistoryFileStat { + id: VlobID + created: DateTime + updated: DateTime + version: VersionInt + size: SizeInt +} + export interface WorkspaceInfo { id: VlobID currentName: EntryName @@ -2047,6 +2055,351 @@ export type WorkspaceGeneratePathAddrError = | WorkspaceGeneratePathAddrErrorOffline | WorkspaceGeneratePathAddrErrorStopped +// WorkspaceHistoryEntryStat +export enum WorkspaceHistoryEntryStatTag { + File = 'WorkspaceHistoryEntryStatFile', + Folder = 'WorkspaceHistoryEntryStatFolder', +} + +export interface WorkspaceHistoryEntryStatFile { + tag: WorkspaceHistoryEntryStatTag.File + id: VlobID + parent: VlobID + created: DateTime + updated: DateTime + version: VersionInt + size: SizeInt +} +export interface WorkspaceHistoryEntryStatFolder { + tag: WorkspaceHistoryEntryStatTag.Folder + id: VlobID + parent: VlobID + created: DateTime + updated: DateTime + version: VersionInt +} +export type WorkspaceHistoryEntryStat = + | WorkspaceHistoryEntryStatFile + | WorkspaceHistoryEntryStatFolder + +// WorkspaceHistoryFdCloseError +export enum WorkspaceHistoryFdCloseErrorTag { + BadFileDescriptor = 'WorkspaceHistoryFdCloseErrorBadFileDescriptor', + Internal = 'WorkspaceHistoryFdCloseErrorInternal', +} + +export interface WorkspaceHistoryFdCloseErrorBadFileDescriptor { + tag: WorkspaceHistoryFdCloseErrorTag.BadFileDescriptor + error: string +} +export interface WorkspaceHistoryFdCloseErrorInternal { + tag: WorkspaceHistoryFdCloseErrorTag.Internal + error: string +} +export type WorkspaceHistoryFdCloseError = + | WorkspaceHistoryFdCloseErrorBadFileDescriptor + | WorkspaceHistoryFdCloseErrorInternal + +// WorkspaceHistoryFdReadError +export enum WorkspaceHistoryFdReadErrorTag { + BadFileDescriptor = 'WorkspaceHistoryFdReadErrorBadFileDescriptor', + BlockNotFound = 'WorkspaceHistoryFdReadErrorBlockNotFound', + Internal = 'WorkspaceHistoryFdReadErrorInternal', + InvalidBlockAccess = 'WorkspaceHistoryFdReadErrorInvalidBlockAccess', + InvalidCertificate = 'WorkspaceHistoryFdReadErrorInvalidCertificate', + InvalidKeysBundle = 'WorkspaceHistoryFdReadErrorInvalidKeysBundle', + NoRealmAccess = 'WorkspaceHistoryFdReadErrorNoRealmAccess', + Offline = 'WorkspaceHistoryFdReadErrorOffline', + Stopped = 'WorkspaceHistoryFdReadErrorStopped', +} + +export interface WorkspaceHistoryFdReadErrorBadFileDescriptor { + tag: WorkspaceHistoryFdReadErrorTag.BadFileDescriptor + error: string +} +export interface WorkspaceHistoryFdReadErrorBlockNotFound { + tag: WorkspaceHistoryFdReadErrorTag.BlockNotFound + error: string +} +export interface WorkspaceHistoryFdReadErrorInternal { + tag: WorkspaceHistoryFdReadErrorTag.Internal + error: string +} +export interface WorkspaceHistoryFdReadErrorInvalidBlockAccess { + tag: WorkspaceHistoryFdReadErrorTag.InvalidBlockAccess + error: string +} +export interface WorkspaceHistoryFdReadErrorInvalidCertificate { + tag: WorkspaceHistoryFdReadErrorTag.InvalidCertificate + error: string +} +export interface WorkspaceHistoryFdReadErrorInvalidKeysBundle { + tag: WorkspaceHistoryFdReadErrorTag.InvalidKeysBundle + error: string +} +export interface WorkspaceHistoryFdReadErrorNoRealmAccess { + tag: WorkspaceHistoryFdReadErrorTag.NoRealmAccess + error: string +} +export interface WorkspaceHistoryFdReadErrorOffline { + tag: WorkspaceHistoryFdReadErrorTag.Offline + error: string +} +export interface WorkspaceHistoryFdReadErrorStopped { + tag: WorkspaceHistoryFdReadErrorTag.Stopped + error: string +} +export type WorkspaceHistoryFdReadError = + | WorkspaceHistoryFdReadErrorBadFileDescriptor + | WorkspaceHistoryFdReadErrorBlockNotFound + | WorkspaceHistoryFdReadErrorInternal + | WorkspaceHistoryFdReadErrorInvalidBlockAccess + | WorkspaceHistoryFdReadErrorInvalidCertificate + | WorkspaceHistoryFdReadErrorInvalidKeysBundle + | WorkspaceHistoryFdReadErrorNoRealmAccess + | WorkspaceHistoryFdReadErrorOffline + | WorkspaceHistoryFdReadErrorStopped + +// WorkspaceHistoryFdStatError +export enum WorkspaceHistoryFdStatErrorTag { + BadFileDescriptor = 'WorkspaceHistoryFdStatErrorBadFileDescriptor', + Internal = 'WorkspaceHistoryFdStatErrorInternal', +} + +export interface WorkspaceHistoryFdStatErrorBadFileDescriptor { + tag: WorkspaceHistoryFdStatErrorTag.BadFileDescriptor + error: string +} +export interface WorkspaceHistoryFdStatErrorInternal { + tag: WorkspaceHistoryFdStatErrorTag.Internal + error: string +} +export type WorkspaceHistoryFdStatError = + | WorkspaceHistoryFdStatErrorBadFileDescriptor + | WorkspaceHistoryFdStatErrorInternal + +// WorkspaceHistoryGetWorkspaceManifestV1TimestampError +export enum WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorTag { + Internal = 'WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInternal', + InvalidCertificate = 'WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidCertificate', + InvalidKeysBundle = 'WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidKeysBundle', + InvalidManifest = 'WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidManifest', + NoRealmAccess = 'WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorNoRealmAccess', + Offline = 'WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorOffline', + Stopped = 'WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorStopped', +} + +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInternal { + tag: WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorTag.Internal + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidCertificate { + tag: WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorTag.InvalidCertificate + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidKeysBundle { + tag: WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorTag.InvalidKeysBundle + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidManifest { + tag: WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorTag.InvalidManifest + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorNoRealmAccess { + tag: WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorTag.NoRealmAccess + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorOffline { + tag: WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorTag.Offline + error: string +} +export interface WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorStopped { + tag: WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorTag.Stopped + error: string +} +export type WorkspaceHistoryGetWorkspaceManifestV1TimestampError = + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInternal + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidCertificate + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidKeysBundle + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorInvalidManifest + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorNoRealmAccess + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorOffline + | WorkspaceHistoryGetWorkspaceManifestV1TimestampErrorStopped + +// WorkspaceHistoryOpenFileError +export enum WorkspaceHistoryOpenFileErrorTag { + EntryNotAFile = 'WorkspaceHistoryOpenFileErrorEntryNotAFile', + EntryNotFound = 'WorkspaceHistoryOpenFileErrorEntryNotFound', + Internal = 'WorkspaceHistoryOpenFileErrorInternal', + InvalidCertificate = 'WorkspaceHistoryOpenFileErrorInvalidCertificate', + InvalidKeysBundle = 'WorkspaceHistoryOpenFileErrorInvalidKeysBundle', + InvalidManifest = 'WorkspaceHistoryOpenFileErrorInvalidManifest', + NoRealmAccess = 'WorkspaceHistoryOpenFileErrorNoRealmAccess', + Offline = 'WorkspaceHistoryOpenFileErrorOffline', + Stopped = 'WorkspaceHistoryOpenFileErrorStopped', +} + +export interface WorkspaceHistoryOpenFileErrorEntryNotAFile { + tag: WorkspaceHistoryOpenFileErrorTag.EntryNotAFile + error: string +} +export interface WorkspaceHistoryOpenFileErrorEntryNotFound { + tag: WorkspaceHistoryOpenFileErrorTag.EntryNotFound + error: string +} +export interface WorkspaceHistoryOpenFileErrorInternal { + tag: WorkspaceHistoryOpenFileErrorTag.Internal + error: string +} +export interface WorkspaceHistoryOpenFileErrorInvalidCertificate { + tag: WorkspaceHistoryOpenFileErrorTag.InvalidCertificate + error: string +} +export interface WorkspaceHistoryOpenFileErrorInvalidKeysBundle { + tag: WorkspaceHistoryOpenFileErrorTag.InvalidKeysBundle + error: string +} +export interface WorkspaceHistoryOpenFileErrorInvalidManifest { + tag: WorkspaceHistoryOpenFileErrorTag.InvalidManifest + error: string +} +export interface WorkspaceHistoryOpenFileErrorNoRealmAccess { + tag: WorkspaceHistoryOpenFileErrorTag.NoRealmAccess + error: string +} +export interface WorkspaceHistoryOpenFileErrorOffline { + tag: WorkspaceHistoryOpenFileErrorTag.Offline + error: string +} +export interface WorkspaceHistoryOpenFileErrorStopped { + tag: WorkspaceHistoryOpenFileErrorTag.Stopped + error: string +} +export type WorkspaceHistoryOpenFileError = + | WorkspaceHistoryOpenFileErrorEntryNotAFile + | WorkspaceHistoryOpenFileErrorEntryNotFound + | WorkspaceHistoryOpenFileErrorInternal + | WorkspaceHistoryOpenFileErrorInvalidCertificate + | WorkspaceHistoryOpenFileErrorInvalidKeysBundle + | WorkspaceHistoryOpenFileErrorInvalidManifest + | WorkspaceHistoryOpenFileErrorNoRealmAccess + | WorkspaceHistoryOpenFileErrorOffline + | WorkspaceHistoryOpenFileErrorStopped + +// WorkspaceHistoryStatEntryError +export enum WorkspaceHistoryStatEntryErrorTag { + EntryNotFound = 'WorkspaceHistoryStatEntryErrorEntryNotFound', + Internal = 'WorkspaceHistoryStatEntryErrorInternal', + InvalidCertificate = 'WorkspaceHistoryStatEntryErrorInvalidCertificate', + InvalidKeysBundle = 'WorkspaceHistoryStatEntryErrorInvalidKeysBundle', + InvalidManifest = 'WorkspaceHistoryStatEntryErrorInvalidManifest', + NoRealmAccess = 'WorkspaceHistoryStatEntryErrorNoRealmAccess', + Offline = 'WorkspaceHistoryStatEntryErrorOffline', + Stopped = 'WorkspaceHistoryStatEntryErrorStopped', +} + +export interface WorkspaceHistoryStatEntryErrorEntryNotFound { + tag: WorkspaceHistoryStatEntryErrorTag.EntryNotFound + error: string +} +export interface WorkspaceHistoryStatEntryErrorInternal { + tag: WorkspaceHistoryStatEntryErrorTag.Internal + error: string +} +export interface WorkspaceHistoryStatEntryErrorInvalidCertificate { + tag: WorkspaceHistoryStatEntryErrorTag.InvalidCertificate + error: string +} +export interface WorkspaceHistoryStatEntryErrorInvalidKeysBundle { + tag: WorkspaceHistoryStatEntryErrorTag.InvalidKeysBundle + error: string +} +export interface WorkspaceHistoryStatEntryErrorInvalidManifest { + tag: WorkspaceHistoryStatEntryErrorTag.InvalidManifest + error: string +} +export interface WorkspaceHistoryStatEntryErrorNoRealmAccess { + tag: WorkspaceHistoryStatEntryErrorTag.NoRealmAccess + error: string +} +export interface WorkspaceHistoryStatEntryErrorOffline { + tag: WorkspaceHistoryStatEntryErrorTag.Offline + error: string +} +export interface WorkspaceHistoryStatEntryErrorStopped { + tag: WorkspaceHistoryStatEntryErrorTag.Stopped + error: string +} +export type WorkspaceHistoryStatEntryError = + | WorkspaceHistoryStatEntryErrorEntryNotFound + | WorkspaceHistoryStatEntryErrorInternal + | WorkspaceHistoryStatEntryErrorInvalidCertificate + | WorkspaceHistoryStatEntryErrorInvalidKeysBundle + | WorkspaceHistoryStatEntryErrorInvalidManifest + | WorkspaceHistoryStatEntryErrorNoRealmAccess + | WorkspaceHistoryStatEntryErrorOffline + | WorkspaceHistoryStatEntryErrorStopped + +// WorkspaceHistoryStatFolderChildrenError +export enum WorkspaceHistoryStatFolderChildrenErrorTag { + EntryIsFile = 'WorkspaceHistoryStatFolderChildrenErrorEntryIsFile', + EntryNotFound = 'WorkspaceHistoryStatFolderChildrenErrorEntryNotFound', + Internal = 'WorkspaceHistoryStatFolderChildrenErrorInternal', + InvalidCertificate = 'WorkspaceHistoryStatFolderChildrenErrorInvalidCertificate', + InvalidKeysBundle = 'WorkspaceHistoryStatFolderChildrenErrorInvalidKeysBundle', + InvalidManifest = 'WorkspaceHistoryStatFolderChildrenErrorInvalidManifest', + NoRealmAccess = 'WorkspaceHistoryStatFolderChildrenErrorNoRealmAccess', + Offline = 'WorkspaceHistoryStatFolderChildrenErrorOffline', + Stopped = 'WorkspaceHistoryStatFolderChildrenErrorStopped', +} + +export interface WorkspaceHistoryStatFolderChildrenErrorEntryIsFile { + tag: WorkspaceHistoryStatFolderChildrenErrorTag.EntryIsFile + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorEntryNotFound { + tag: WorkspaceHistoryStatFolderChildrenErrorTag.EntryNotFound + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorInternal { + tag: WorkspaceHistoryStatFolderChildrenErrorTag.Internal + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorInvalidCertificate { + tag: WorkspaceHistoryStatFolderChildrenErrorTag.InvalidCertificate + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorInvalidKeysBundle { + tag: WorkspaceHistoryStatFolderChildrenErrorTag.InvalidKeysBundle + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorInvalidManifest { + tag: WorkspaceHistoryStatFolderChildrenErrorTag.InvalidManifest + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorNoRealmAccess { + tag: WorkspaceHistoryStatFolderChildrenErrorTag.NoRealmAccess + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorOffline { + tag: WorkspaceHistoryStatFolderChildrenErrorTag.Offline + error: string +} +export interface WorkspaceHistoryStatFolderChildrenErrorStopped { + tag: WorkspaceHistoryStatFolderChildrenErrorTag.Stopped + error: string +} +export type WorkspaceHistoryStatFolderChildrenError = + | WorkspaceHistoryStatFolderChildrenErrorEntryIsFile + | WorkspaceHistoryStatFolderChildrenErrorEntryNotFound + | WorkspaceHistoryStatFolderChildrenErrorInternal + | WorkspaceHistoryStatFolderChildrenErrorInvalidCertificate + | WorkspaceHistoryStatFolderChildrenErrorInvalidKeysBundle + | WorkspaceHistoryStatFolderChildrenErrorInvalidManifest + | WorkspaceHistoryStatFolderChildrenErrorNoRealmAccess + | WorkspaceHistoryStatFolderChildrenErrorOffline + | WorkspaceHistoryStatFolderChildrenErrorStopped + // WorkspaceInfoError export enum WorkspaceInfoErrorTag { Internal = 'WorkspaceInfoErrorInternal', @@ -2864,6 +3217,53 @@ export interface LibParsecPlugin { workspace: Handle, path: FsPath ): Promise> + workspaceHistoryFdClose( + workspace: Handle, + fd: FileDescriptor + ): Promise> + workspaceHistoryFdRead( + workspace: Handle, + fd: FileDescriptor, + offset: U64, + size: U64 + ): Promise> + workspaceHistoryFdStat( + workspace: Handle, + fd: FileDescriptor + ): Promise> + workspaceHistoryGetWorkspaceManifestV1Timestamp( + workspace: Handle + ): Promise> + workspaceHistoryOpenFile( + workspace: Handle, + at: DateTime, + path: FsPath + ): Promise> + workspaceHistoryOpenFileById( + workspace: Handle, + at: DateTime, + entry_id: VlobID + ): Promise> + workspaceHistoryStatEntry( + workspace: Handle, + at: DateTime, + path: FsPath + ): Promise> + workspaceHistoryStatEntryById( + workspace: Handle, + at: DateTime, + entry_id: VlobID + ): Promise> + workspaceHistoryStatFolderChildren( + workspace: Handle, + at: DateTime, + path: FsPath + ): Promise, WorkspaceHistoryStatFolderChildrenError>> + workspaceHistoryStatFolderChildrenById( + workspace: Handle, + at: DateTime, + entry_id: VlobID + ): Promise, WorkspaceHistoryStatFolderChildrenError>> workspaceInfo( workspace: Handle ): Promise> From a9995595fe2713210c22931fec0f1a816e8cd424 Mon Sep 17 00:00:00 2001 From: Maxime GRANDCOLAS Date: Thu, 10 Oct 2024 14:49:51 +0200 Subject: [PATCH 7/8] [MS] Full implementation of workspace history Co-authored-by: fabienSvstr --- client/package-lock.json | 30 +- client/package.json | 2 +- client/src/components/files/FileCard.vue | 2 + client/src/components/files/FileDropZone.vue | 15 +- .../src/components/files/FileGridDisplay.vue | 4 + .../src/components/files/FileListDisplay.vue | 4 + client/src/components/files/FileListItem.vue | 11 +- .../src/components/files/FileRestoreItem.vue | 317 ++++++++++++ .../components/files/HistoryFileListItem.vue | 115 +++++ client/src/components/files/index.ts | 16 +- client/src/components/files/types.ts | 81 ++- .../components/header/HeaderBreadcrumbs.vue | 6 +- client/src/components/workspaces/utils.ts | 4 + client/src/locales/en-US.json | 23 +- client/src/locales/fr-FR.json | 25 +- client/src/parsec/history.ts | 181 +++++++ client/src/parsec/index.ts | 1 + client/src/parsec/login.ts | 4 + client/src/parsec/mock_generator.ts | 6 +- client/src/parsec/types.ts | 36 +- client/src/parsec/workspace.ts | 10 + client/src/router/checks.ts | 1 + client/src/router/params.ts | 2 +- client/src/router/types.ts | 6 + client/src/services/fileOperationManager.ts | 209 +++++++- client/src/theme/components/modals.scss | 6 + client/src/views/files/FileContextMenu.vue | 2 +- client/src/views/files/FileOperationMenu.vue | 49 +- client/src/views/files/FoldersPage.vue | 19 +- client/src/views/header/HeaderPage.vue | 21 +- .../views/workspaces/WorkspaceContextMenu.vue | 2 +- .../views/workspaces/WorkspaceHistoryPage.vue | 475 ++++++++++++++++++ .../pw/e2e/document_context_menu.spec.ts | 6 +- client/tests/pw/e2e/file_details.spec.ts | 8 +- .../tests/pw/e2e/workspaces_actions.spec.ts | 6 +- 35 files changed, 1654 insertions(+), 51 deletions(-) create mode 100644 client/src/components/files/FileRestoreItem.vue create mode 100644 client/src/components/files/HistoryFileListItem.vue create mode 100644 client/src/parsec/history.ts create mode 100644 client/src/views/workspaces/WorkspaceHistoryPage.vue diff --git a/client/package-lock.json b/client/package-lock.json index d8833087f0b..d41ab676b2c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -26,7 +26,7 @@ "@types/luxon": "^3.3.4", "axios": "^1.7.4", "luxon": "^3.4.4", - "megashark-lib": "git+https://github.com/Scille/megashark-lib.git#d1ebfc3607f9d31df7a6e6430a6dbceafd9099d5", + "megashark-lib": "git+https://github.com/Scille/megashark-lib.git#0e9973cb77a7ed70743a59943d6ef4e060a17c52", "qrcode-vue3": "^1.6.8", "uuid": "^9.0.1", "vue": "^3.3.8", @@ -3468,6 +3468,20 @@ "@vue/language-core": "1.8.20" } }, + "node_modules/@vuepic/vue-datepicker": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-9.0.3.tgz", + "integrity": "sha512-OtCAKG+CkVBpCwnPx7/BGRF+/z+jDzNl2cMBpcr8j2NkAN+13xkUt7sctbOVKbG/jhuXtKoUSedI69e0cDXPXw==", + "dependencies": { + "date-fns": "^3.6.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "vue": ">=3.2.0" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", @@ -5534,6 +5548,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", @@ -9366,11 +9389,12 @@ }, "node_modules/megashark-lib": { "version": "0.0.1", - "resolved": "git+ssh://git@github.com/Scille/megashark-lib.git#d1ebfc3607f9d31df7a6e6430a6dbceafd9099d5", - "integrity": "sha512-W7ic8VEdwUc563NQ6oDiYGUaOljtfLi/uzry1am80GOnJB5U6sS7YZ+v1RYKFi54eAVHV3dyu8/RxaLubXgrnQ==", + "resolved": "git+ssh://git@github.com/Scille/megashark-lib.git#0e9973cb77a7ed70743a59943d6ef4e060a17c52", + "integrity": "sha512-7pRLhl49XDg5yk3q2jjw1pBohCvAWLudJwEunLw17z2weEepXewGXqU8WF0pCmqA76tMwYtlFyPi1h+3s3clVg==", "dependencies": { "@ionic/vue": "^7.0.0", "@stripe/stripe-js": "^4.0.0", + "@vuepic/vue-datepicker": "^9.0.3", "axios": "^1.7.2", "ionicons": "^7.0.0", "luxon": "^3.4.4", diff --git a/client/package.json b/client/package.json index 06374d8b13c..dd46beae28f 100644 --- a/client/package.json +++ b/client/package.json @@ -53,7 +53,7 @@ "@types/luxon": "^3.3.4", "axios": "^1.7.4", "luxon": "^3.4.4", - "megashark-lib": "git+https://github.com/Scille/megashark-lib.git#d1ebfc3607f9d31df7a6e6430a6dbceafd9099d5", + "megashark-lib": "git+https://github.com/Scille/megashark-lib.git#0e9973cb77a7ed70743a59943d6ef4e060a17c52", "qrcode-vue3": "^1.6.8", "uuid": "^9.0.1", "vue": "^3.3.8", diff --git a/client/src/components/files/FileCard.vue b/client/src/components/files/FileCard.vue index ef15584e945..de839290db0 100644 --- a/client/src/components/files/FileCard.vue +++ b/client/src/components/files/FileCard.vue @@ -17,6 +17,7 @@ :current-path="currentPath" @files-added="$emit('filesAdded', $event)" :is-reader="isWorkspaceReader" + @drop-as-reader="$emit('dropAsReader')" >
@@ -101,6 +102,7 @@ const emits = defineEmits<{ (e: 'click', event: Event, entry: EntryModel): void; (e: 'menuClick', event: Event, entry: EntryModel, onFinished: () => void): void; (e: 'filesAdded', imports: FileImportTuple[]): void; + (e: 'dropAsReader'): void; }>(); defineExpose({ diff --git a/client/src/components/files/FileDropZone.vue b/client/src/components/files/FileDropZone.vue index 98e470f98c8..b9484f7e71d 100644 --- a/client/src/components/files/FileDropZone.vue +++ b/client/src/components/files/FileDropZone.vue @@ -34,8 +34,7 @@ import { DocumentImport, MsImage } from 'megashark-lib'; import { FileImportTuple, getFilesFromDrop } from '@/components/files/utils'; import { FsPath } from '@/parsec'; import { IonLabel } from '@ionic/vue'; -import { computed, inject, onMounted, onUnmounted, ref } from 'vue'; -import { Information, InformationLevel, InformationManager, InformationManagerKey, PresentationMode } from '@/services/informationManager'; +import { computed, onMounted, onUnmounted, ref } from 'vue'; defineExpose({ reset, @@ -50,10 +49,10 @@ const props = defineProps<{ const emits = defineEmits<{ (e: 'filesAdded', imports: FileImportTuple[]): void; + (e: 'dropAsReader'): void; }>(); const dragEnterCount = ref(0); -const informationManager: InformationManager = inject(InformationManagerKey)!; const isActive = computed(() => { return !props.disabled && !props.isReader && dragEnterCount.value > 0; @@ -77,19 +76,13 @@ onUnmounted(() => { async function onDrop(event: DragEvent): Promise { if (props.isReader) { - await informationManager.present( - new Information({ - message: 'FoldersPage.ImportFile.noDropForReader', - level: InformationLevel.Error, - }), - PresentationMode.Toast, - ); + event.stopPropagation(); + emits('dropAsReader'); return; } if (props.disabled) { return; } - event.stopPropagation(); dragEnterCount.value = 0; const imports = await getFilesFromDrop(event, props.currentPath); if (imports.length) { diff --git a/client/src/components/files/FileGridDisplay.vue b/client/src/components/files/FileGridDisplay.vue index 474d039f926..8f8a948b6ad 100644 --- a/client/src/components/files/FileGridDisplay.vue +++ b/client/src/components/files/FileGridDisplay.vue @@ -7,6 +7,7 @@ :show-drop-message="true" @files-added="$emit('filesAdded', $event)" :is-reader="ownRole === WorkspaceRole.Reader" + @drop-as-reader="$emit('dropAsReader')" >
void): void; (e: 'globalMenuClick', event: Event): void; (e: 'filesAdded', imports: FileImportTuple[]): void; + (e: 'dropAsReader'): void; }>(); onMounted(async () => { diff --git a/client/src/components/files/FileListDisplay.vue b/client/src/components/files/FileListDisplay.vue index 8d14d216077..bf7bb2e9f92 100644 --- a/client/src/components/files/FileListDisplay.vue +++ b/client/src/components/files/FileListDisplay.vue @@ -8,6 +8,7 @@ @files-added="$emit('filesAdded', $event)" :show-drop-message="true" :is-reader="ownRole === WorkspaceRole.Reader" + @drop-as-reader="$emit('dropAsReader')" >
void): void; (e: 'globalMenuClick', event: Event): void; (e: 'filesAdded', imports: FileImportTuple[]): void; + (e: 'dropAsReader'): void; }>(); const fileDropZoneRef = ref(); diff --git a/client/src/components/files/FileListItem.vue b/client/src/components/files/FileListItem.vue index 123e997856b..803d05b8718 100644 --- a/client/src/components/files/FileListItem.vue +++ b/client/src/components/files/FileListItem.vue @@ -2,11 +2,12 @@