add ngcli get :j:k and fix bug in stores in tauri

master
Niko PLP 4 months ago
parent 48c63ead2f
commit 856d713a73
  1. 12
      nextgraph/src/local_broker.rs
  2. 2
      ng-app/src/lib/Document.svelte
  3. 2
      ng-app/src/lib/Login.svelte
  4. 2
      ng-app/src/lib/panes/History.svelte
  5. 15
      ng-app/src/lib/popups/Signature.svelte
  6. 10
      ng-app/src/locales/en.json
  7. 5
      ng-app/src/routes/WalletLogin.svelte
  8. 29
      ng-net/src/actors/client/pin_repo.rs
  9. 2
      ng-net/src/app_protocol.rs
  10. 23
      ng-repo/src/branch.rs
  11. 2
      ng-repo/src/file.rs
  12. 26
      ng-repo/src/types.rs
  13. 102
      ng-verifier/src/verifier.rs
  14. 6
      ng-wallet/src/lib.rs
  15. 2
      ngaccount/web/tailwind.config.cjs
  16. 180
      ngcli/src/main.rs

@ -1313,9 +1313,13 @@ impl LocalBroker {
// key // key
// ); // );
let site = opened_wallet.wallet.site(&user_id)?; let locator = if let Ok(site) = opened_wallet.wallet.site(&user_id) {
let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover) let core = site.cores[0]; //TODO: cycle the other cores if failure to connect (failover)
let brokers = opened_wallet.wallet.broker(core.0)?; let brokers = opened_wallet.wallet.broker(core.0)?;
BrokerInfoV0::vec_into_locator(brokers)
} else {
Locator::empty()
};
key_material.zeroize(); key_material.zeroize();
let mut verifier = Verifier::new( let mut verifier = Verifier::new(
@ -1330,7 +1334,7 @@ impl LocalBroker {
private_store_id: credentials.2, private_store_id: credentials.2,
protected_store_id: credentials.3, protected_store_id: credentials.3,
public_store_id: credentials.4, public_store_id: credentials.4,
locator: BrokerInfoV0::vec_into_locator(brokers), locator,
}, },
block_storage, block_storage,
)?; )?;

@ -152,7 +152,7 @@
</div> </div>
</div> </div>
{:then app} {:then app}
<div class:max-w-screen-lg={center} class="flex flex-col" style="overflow-wrap: anywhere;" class:w-[1024px]={center} > <div class:max-w-screen-lg={center} class="flex flex-col break-all" style="overflow-wrap: anywhere;" class:w-[1024px]={center} >
<svelte:component this={app} commits={$commits}/> <svelte:component this={app} commits={$commits}/>
</div> </div>
{/await} {/await}

@ -258,7 +258,7 @@
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
if (e.message.includes("constructor") || (typeof e === "string" && e.includes("constructor") )) e = "BrowserTooOld"; if (e.message && e.message.includes("constructor") || (typeof e === "string" && e.includes("constructor") )) e = "BrowserTooOld";
error = e; error = e;
step = "end"; step = "end";
dispatch("error", { error: e }); dispatch("error", { error: e });

@ -107,7 +107,7 @@
} }
}); });
get(branch).history.start(); get(branch).history.start();
},1); },100);
}); });
onDestroy( ()=>{ onDestroy( ()=>{

@ -91,9 +91,8 @@
</script> </script>
<div class="flex flex-col"> <div class="flex flex-col">
<span class="font-bold text-xl">Signature</span> <span class="font-bold text-xl">{$t("doc.signature.title")}</span>
{$t("current_heads")} :
Current heads :
{#each heads as head} {#each heads as head}
{#if head[1]} {#if head[1]}
<div style="font-family: monospace; font: Courier; font-size:16px;" class="flex text-green-600 clickable my-2" <div style="font-family: monospace; font: Courier; font-size:16px;" class="flex text-green-600 clickable my-2"
@ -119,7 +118,7 @@
<ShieldCheck tabindex="-1" class="mr-2 focus:outline-none" /> <ShieldCheck tabindex="-1" class="mr-2 focus:outline-none" />
{$t("doc.sign_snapshot")} {$t("doc.sign_snapshot")}
</Button> </Button>
<span class="mb-2">or click on one of the signed heads to get its link.</span> <span class="mb-2">{$t("doc.signature.or_click_on_head")}</span>
{:else if can_sign} {:else if can_sign}
<button <button
@ -136,15 +135,15 @@
bind:checked={ snapshot } bind:checked={ snapshot }
><span class="text-gray-700 text-base">{$t("doc.take_snapshot")}</span> ><span class="text-gray-700 text-base">{$t("doc.take_snapshot")}</span>
</Toggle> </Toggle>
{#if has_signatures}<span>or click on one of the signed heads to get its link</span>{/if} {#if has_signatures}<span>{$t("doc.signature.or_click_on_head")}</span>{/if}
{:else} {:else}
<div class="flex mt-3"><Camera tabindex="-1" class="w-6 h-6 mr-3 text-green-600"/><span class="text-green-600">A signed snapshot is currently at the head.</span></div> <div class="flex mt-3"><Camera tabindex="-1" class="w-6 h-6 mr-3 text-green-600"/><span class="text-green-600">{$t("doc.signature.signed_snap_at_head")}</span></div>
<span>Here is its link that you can share.<br/>For now this link is only usable with the CLI, by running the following command :<br/><br/></span> <span>{$t("doc.signature.here_is_the_link")}<br/>{$t("doc.signature.cli_warning")} :<br/><br/></span>
<span style="font-family: monospace; font: Courier; font-size:16px;" class="break-all">ngcli get {signed_commit_link(heads[0])}</span> <span style="font-family: monospace; font: Courier; font-size:16px;" class="break-all">ngcli get {signed_commit_link(heads[0])}</span>
{/if} {/if}
{/if} {/if}
{#if (force_snapshot || can_sign) && cur_link } {#if (force_snapshot || can_sign) && cur_link }
<span class="mt-3">For now the link is only usable with the CLI, by running the following command :<br/><br/></span> <span class="mt-3">{$t("doc.signature.cli_warning")} :<br/><br/></span>
<span style="font-family: monospace; font: Courier; font-size:16px;" class="break-all">ngcli get {cur_link}</span> <span style="font-family: monospace; font: Courier; font-size:16px;" class="break-all">ngcli get {cur_link}</span>
{/if} {/if}
</div> </div>

@ -74,6 +74,14 @@
"chat": "Chat" "chat": "Chat"
} }
}, },
"signature": {
"title": "Signature",
"current_heads": "Current heads",
"or_click_on_head": "or click on one of the signed heads to get its link.",
"here_is_the_link": "Here is its link that you can share.",
"cli_warning": "For now the link is only usable with the CLI, by running the following command",
"signed_snap_at_head": "A signed snapshot is currently at the head."
},
"file": { "file": {
"download": "Download", "download": "Download",
"upload_progress": "Uploading...", "upload_progress": "Uploading...",
@ -475,7 +483,7 @@
"download_wallet_done": "Your wallet file has been downloaded into your \"Downloads\" folder, with the name<br /><span class=\"text-black\"> {download_name}</span ><br /> <span class=\"font-bold\" >Please move it to a safe and durable place.</span ><br />", "download_wallet_done": "Your wallet file has been downloaded into your \"Downloads\" folder, with the name<br /><span class=\"text-black\"> {download_name}</span ><br /> <span class=\"font-bold\" >Please move it to a safe and durable place.</span ><br />",
"download_pdf_description": "Please download your Recovery PDF, print it, and delete it.", "download_pdf_description": "Please download your Recovery PDF, print it, and delete it.",
"download_pdf": "Download my PDF", "download_pdf": "Download my PDF",
"download_pdf_done": "Your Recovery PDF file has been downloaded into your \"Downloads\" folder, with the name<br /><span class=\"text-black\"> {pdf_name}</span ><br /> <span class=\"font-bold\" >Please print it and then delete it.</span ><br />", "download_pdf_done": "Your Recovery PDF file has been downloaded into your \"Downloads\" folder,<br /> with the name <span class=\"text-black\"> {pdf_name}</span ><br /><br /> <span class=\"font-bold\" >Please print it and then delete it.</span ><br /><br />",
"your_pazzle": "Here below is your Pazzle. <br /> The <span class=\"font-bold\">order</span> of each image is <span class=\"font-bold\">important</span>!", "your_pazzle": "Here below is your Pazzle. <br /> The <span class=\"font-bold\">order</span> of each image is <span class=\"font-bold\">important</span>!",
"your_mnemonic": "And here is your mnemonic (your alternative passphrase):", "your_mnemonic": "And here is your mnemonic (your alternative passphrase):",
"unlock_tips_1": "You can use both the pazzle or the mnemonic to unlock your wallet. The pazzle is easier to remember. The mnemonic is useful in some special cases. We recommend that you use the pazzle. <span class=\"font-bold text-xl\" >Copy both on a piece of paper.</span > You should try to memorize the pazzle. Once you did, you won't need the paper anymore.", "unlock_tips_1": "You can use both the pazzle or the mnemonic to unlock your wallet. The pazzle is easier to remember. The mnemonic is useful in some special cases. We recommend that you use the pazzle. <span class=\"font-bold text-xl\" >Copy both on a piece of paper.</span > You should try to memorize the pazzle. Once you did, you won't need the paper anymore.",

@ -73,6 +73,8 @@
}); });
active_wallet_unsub = active_wallet.subscribe(async (value) => { active_wallet_unsub = active_wallet.subscribe(async (value) => {
if (value && value.wallet) { if (value && value.wallet) {
step = "loggedin";
await tick();
if (!$active_session) { if (!$active_session) {
try { try {
let session = await ng.session_start( let session = await ng.session_start(
@ -85,6 +87,7 @@
loggedin(); loggedin();
} }
} catch (e) { } catch (e) {
step = "open";
error = e; error = e;
importing = false; importing = false;
wallet = undefined; wallet = undefined;
@ -105,7 +108,7 @@
}); });
function loggedin() { async function loggedin() {
step = "loggedin"; step = "loggedin";
if ($redirect_after_login) { if ($redirect_after_login) {
let redir=$redirect_after_login; let redir=$redirect_after_login;

@ -27,6 +27,35 @@ impl PinRepo {
pub fn get_actor(&self, id: i64) -> Box<dyn EActor> { pub fn get_actor(&self, id: i64) -> Box<dyn EActor> {
Actor::<PinRepo, RepoOpened>::new_responder(id) Actor::<PinRepo, RepoOpened>::new_responder(id)
} }
pub fn for_branch(repo: &Repo, branch: &BranchId, broker_id: &DirectPeerId) -> PinRepo {
let overlay = OverlayAccess::new_write_access_from_store(&repo.store);
let mut rw_topics = Vec::with_capacity(1);
let mut ro_topics = vec![];
let branch = repo.branches.get(branch).unwrap();
if let Some(privkey) = &branch.topic_priv_key {
rw_topics.push(PublisherAdvert::new(
branch.topic.unwrap(),
privkey.clone(),
*broker_id,
));
} else {
ro_topics.push(branch.topic.unwrap());
}
PinRepo::V0(PinRepoV0 {
hash: repo.id.into(),
overlay,
// TODO: overlay_root_topic
overlay_root_topic: None,
expose_outer: false,
peers: vec![],
max_peer_count: 0,
//allowed_peers: vec![],
ro_topics,
rw_topics,
})
}
pub fn from_repo(repo: &Repo, broker_id: &DirectPeerId) -> PinRepo { pub fn from_repo(repo: &Repo, broker_id: &DirectPeerId) -> PinRepo {
let overlay = OverlayAccess::new_write_access_from_store(&repo.store); let overlay = OverlayAccess::new_write_access_from_store(&repo.store);
let mut rw_topics = Vec::with_capacity(repo.branches.len()); let mut rw_topics = Vec::with_capacity(repo.branches.len());

@ -41,7 +41,7 @@ lazy_static! {
Regex::new(r"^did:ng:o:([A-Za-z0-9-_]*):v:([A-Za-z0-9-_]*):a:([A-Za-z0-9-_%]*)$").unwrap(); //TODO: allow international chars. disallow digit as first char Regex::new(r"^did:ng:o:([A-Za-z0-9-_]*):v:([A-Za-z0-9-_]*):a:([A-Za-z0-9-_%]*)$").unwrap(); //TODO: allow international chars. disallow digit as first char
#[doc(hidden)] #[doc(hidden)]
static ref RE_OBJECTS: Regex = static ref RE_OBJECTS: Regex =
Regex::new(r"^did:ng(?::o:([A-Za-z0-9-_]{44}))?:v:([A-Za-z0-9-_]{44})((?::c:[A-Za-z0-9-_]{44}:k:[A-Za-z0-9-_]{44})+)(?::s:([A-Za-z0-9-_]{44}):k:([A-Za-z0-9-_]{44}))?:l:([A-Za-z0-9-_]*)$").unwrap(); Regex::new(r"^did:ng(?::o:([A-Za-z0-9-_]{44}))?:v:([A-Za-z0-9-_]{44})((?::[cj]:[A-Za-z0-9-_]{44}:k:[A-Za-z0-9-_]{44})+)(?::s:([A-Za-z0-9-_]{44}):k:([A-Za-z0-9-_]{44}))?:l:([A-Za-z0-9-_]*)$").unwrap();
#[doc(hidden)] #[doc(hidden)]
static ref RE_OBJECT_READ_CAPS: Regex = static ref RE_OBJECT_READ_CAPS: Regex =
Regex::new(r":[cj]:([A-Za-z0-9-_]{44}):k:([A-Za-z0-9-_]{44})").unwrap(); Regex::new(r":[cj]:([A-Za-z0-9-_]{44}):k:([A-Za-z0-9-_]{44})").unwrap();

@ -196,14 +196,21 @@ impl Branch {
// check if this commit object is present in theirs or has already been visited in the current walk // check if this commit object is present in theirs or has already been visited in the current walk
// load deps, stop at the root(including it in visited) or if this is a commit object from known_heads // load deps, stop at the root(including it in visited) or if this is a commit object from known_heads
let found_in_filter = if let Some(filter) = theirs_filter { let mut found_in_theirs = theirs.contains(&id);
let hash = id.get_hash(); if !found_in_theirs {
filter.contains_hash(hash) found_in_theirs = if let Some(filter) = theirs_filter {
} else { let hash = id.get_hash();
false filter.contains_hash(hash)
}; } else {
false
};
}
if !found_in_filter && !theirs.contains(&id) { if found_in_theirs {
if theirs_found.is_some() {
theirs_found.as_mut().unwrap().insert(id);
}
} else {
if let Some(past) = visited.get_mut(&id) { if let Some(past) = visited.get_mut(&id) {
// we update the future // we update the future
if let Some(f) = future { if let Some(f) = future {
@ -238,8 +245,6 @@ impl Branch {
// } // }
// } // }
} }
} else if theirs_found.is_some() {
theirs_found.as_mut().unwrap().insert(id);
} }
} }
Err(ObjectParseError::MissingBlocks(blocks)) => { Err(ObjectParseError::MissingBlocks(blocks)) => {

@ -1498,7 +1498,7 @@ mod test {
) )
.expect("open"); .expect("open");
// this only works because we chose a big block size (1MB) so the small JPG file files in one block. // this only works because we chose a big block size (1MB) so the small JPG file fits in one block.
// if not, we would have to call read repeatedly and append the results into a buffer, in order to get the full file // if not, we would have to call read repeatedly and append the results into a buffer, in order to get the full file
let res = file.read(0, len).expect("read all"); let res = file.read(0, len).expect("read all");

@ -83,6 +83,32 @@ impl Digest {
} }
hasher.finish() hasher.finish()
} }
pub fn print_all(all: &[Digest]) -> String {
all.iter()
.map(|d| d.to_string())
.collect::<Vec<String>>()
.join(" ")
}
pub fn print_iter(all: impl Iterator<Item = Digest>) -> String {
all.map(|d| d.to_string())
.collect::<Vec<String>>()
.join(" ")
}
pub fn print_iter_ref<'a>(all: impl Iterator<Item = &'a Digest>) -> String {
all.map(|d| d.to_string())
.collect::<Vec<String>>()
.join(" ")
}
pub fn print_all_ref(all: &[&Digest]) -> String {
all.into_iter()
.map(|d| d.to_string())
.collect::<Vec<String>>()
.join(" ")
}
} }
impl fmt::Display for Digest { impl fmt::Display for Digest {

@ -154,6 +154,11 @@ impl Verifier {
self.config.public_store_id.as_ref().unwrap() self.config.public_store_id.as_ref().unwrap()
} }
pub fn update_locator(&mut self, locator: Locator) {
self.outer = NuriV0::locator(&locator);
self.config.locator = locator;
}
pub async fn close(&self) { pub async fn close(&self) {
log_debug!("VERIFIER CLOSED {}", self.user_id()); log_debug!("VERIFIER CLOSED {}", self.user_id());
BROKER BROKER
@ -301,7 +306,7 @@ impl Verifier {
branch_id: BranchId, branch_id: BranchId,
store_repo: StoreRepo, store_repo: StoreRepo,
) -> Result<(Receiver<AppResponse>, CancelFn), VerifierError> { ) -> Result<(Receiver<AppResponse>, CancelFn), VerifierError> {
//log_info!("#### create_branch_subscription {}", branch); //log_info!("#### create_branch_subscription {}", branch_id);
let (tx, rx) = mpsc::unbounded::<AppResponse>(); let (tx, rx) = mpsc::unbounded::<AppResponse>();
//log_info!("SUBSCRIBE"); //log_info!("SUBSCRIBE");
if let Some(returned) = self.branch_subscriptions.insert(branch_id, tx.clone()) { if let Some(returned) = self.branch_subscriptions.insert(branch_id, tx.clone()) {
@ -1181,9 +1186,15 @@ impl Verifier {
let user = self.user_id().clone(); let user = self.user_id().clone();
let broker = BROKER.read().await; let broker = BROKER.read().await;
log_debug!("looping on branches {:?}", branches); // log_debug!(
// "looping on branches {:?}",
// branches
// .iter()
// .map(|(_, b, _)| b.to_string())
// .collect::<Vec<String>>()
// );
for (repo, branch, publisher) in branches { for (repo, branch, publisher) in branches {
log_debug!("open_branch_ repo {} branch {}", repo, branch); //log_debug!("open_branch_ repo {} branch {}", repo, branch);
let _e = self let _e = self
.open_branch_( .open_branch_(
&repo, &repo,
@ -1195,12 +1206,12 @@ impl Verifier {
false, false,
) )
.await; .await;
log_debug!( // log_debug!(
"END OF open_branch_ repo {} branch {} with {:?}", // "END OF open_branch_ repo {} branch {} with {:?}",
repo, // repo,
branch, // branch,
_e // _e
); // );
// discarding error. // discarding error.
} }
Ok(()) Ok(())
@ -1296,7 +1307,11 @@ impl Verifier {
} }
} }
}; };
//log_info!("need_open {} need_sub {}", need_open, need_sub); // log_info!(
// "OPEN BRANCH {branch} need_open {} need_sub {}",
// need_open,
// need_sub
// );
let remote = remote_broker.into(); let remote = remote_broker.into();
@ -1316,7 +1331,9 @@ impl Verifier {
let (pin_req, topic_id) = { let (pin_req, topic_id) = {
let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?; let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?;
let topic_id = repo.branch(branch).unwrap().topic.unwrap(); let topic_id = repo.branch(branch).unwrap().topic.unwrap();
//TODO: only pin the requested branch. // TODO only pinning the requested branch.
// let pin_req =
// PinRepo::for_branch(repo, branch, remote_broker.broker_peer_id());
let pin_req = PinRepo::from_repo(repo, remote_broker.broker_peer_id()); let pin_req = PinRepo::from_repo(repo, remote_broker.broker_peer_id());
(pin_req, topic_id) (pin_req, topic_id)
}; };
@ -1330,19 +1347,25 @@ impl Verifier {
//TODO: check that in the returned opened_repo, the branch we are interested in has effectively been subscribed as publisher by the broker. //TODO: check that in the returned opened_repo, the branch we are interested in has effectively been subscribed as publisher by the broker.
for topic in opened { for topic in opened {
if topic.topic_id() == &topic_id { let (_, branch_id) = self
self.do_sync_req_if_needed( .topics
broker, .get(&(overlay, *topic.topic_id()))
user, .ok_or(NgError::TopicNotFound)?
&remote, .to_owned();
branch,
repo_id, //if topic.topic_id() == &topic_id {
topic.known_heads(), self.do_sync_req_if_needed(
topic.commits_nbr(), broker,
) user,
.await?; &remote,
break; &branch_id,
} repo_id,
topic.known_heads(),
topic.commits_nbr(),
)
.await?;
//break;
//}
} }
} }
Ok(_) => return Err(NgError::InvalidResponse), Ok(_) => return Err(NgError::InvalidResponse),
@ -1408,7 +1431,6 @@ impl Verifier {
Ok(SoS::Single(sub)) => { Ok(SoS::Single(sub)) => {
let repo = self.repos.get_mut(&repo_id).ok_or(NgError::RepoNotFound)?; let repo = self.repos.get_mut(&repo_id).ok_or(NgError::RepoNotFound)?;
Self::branch_was_opened(&self.topics, repo, &sub)?; Self::branch_was_opened(&self.topics, repo, &sub)?;
self.do_sync_req_if_needed( self.do_sync_req_if_needed(
broker, broker,
user, user,
@ -1706,7 +1728,12 @@ impl Verifier {
remote_commits_nbr: u64, remote_commits_nbr: u64,
) -> Result<(), NgError> { ) -> Result<(), NgError> {
let (store, msg, branch_secret) = { let (store, msg, branch_secret) = {
//log_info!("do_sync_req_if_needed for branch {}", branch_id); // log_info!(
// "do_sync_req_if_needed for branch {} {} {}",
// branch_id,
// remote_commits_nbr,
// Digest::print_all(remote_heads)
// );
if remote_commits_nbr == 0 || remote_heads.is_empty() { if remote_commits_nbr == 0 || remote_heads.is_empty() {
log_debug!("branch is new on the broker. doing nothing"); log_debug!("branch is new on the broker. doing nothing");
return Ok(()); return Ok(());
@ -1726,7 +1753,11 @@ impl Verifier {
&& theirs.difference(&ours_set).count() == 0 && theirs.difference(&ours_set).count() == 0
{ {
// no need to sync // no need to sync
log_info!("branch {} is up to date", branch_id); log_debug!(
"branch {} is up to date at heads {}",
branch_id,
Digest::print_iter(ours)
);
return Ok(()); return Ok(());
} }
@ -1736,6 +1767,11 @@ impl Verifier {
let mut recursor: Vec<(ObjectId, Option<ObjectId>)> = let mut recursor: Vec<(ObjectId, Option<ObjectId>)> =
ours_set.iter().map(|h| (h.clone(), None)).collect(); ours_set.iter().map(|h| (h.clone(), None)).collect();
// log_debug!(
// "SEARCHING FOR THEIR HEADS from OURS {}",
// Digest::print_iter(ours)
// );
let _ = Branch::load_causal_past( let _ = Branch::load_causal_past(
&mut recursor, &mut recursor,
&repo.store, &repo.store,
@ -1745,6 +1781,12 @@ impl Verifier {
&mut Some(&mut theirs_found), &mut Some(&mut theirs_found),
&None, &None,
); );
// log_debug!(
// "FOUND THEIR HEADS {}",
// Digest::print_iter_ref(theirs_found.iter())
// );
// for our in ours_set.iter() { // for our in ours_set.iter() {
// //log_info!("OUR HEADS {}", our); // //log_info!("OUR HEADS {}", our);
// if let Ok(cobj) = Object::load(*our, None, &repo.store) { // if let Ok(cobj) = Object::load(*our, None, &repo.store) {
@ -1756,6 +1798,7 @@ impl Verifier {
theirs.difference(&theirs_found).cloned().collect(); theirs.difference(&theirs_found).cloned().collect();
let known_commits = if theirs_not_found.is_empty() { let known_commits = if theirs_not_found.is_empty() {
//log_debug!("local heads are newer than remote");
return Ok(()); return Ok(());
} else { } else {
if visited.is_empty() { if visited.is_empty() {
@ -1892,10 +1935,13 @@ impl Verifier {
) )
.await?; .await?;
let repo = self.get_repo_mut(&repo_id, store.get_store_repo())?;
// for (b, _) in repo.branches.iter() {
// let _ = repo.opened_branches.insert(b.clone(), true);
// }
// adding the Store branch to the opened_branches // adding the Store branch to the opened_branches
// TODO: only do it if the Store is 3P. // TODO: only do it if the Store is 3P.
if let Some(store_branch_id) = store_branch { if let Some(store_branch_id) = store_branch {
let repo = self.get_repo_mut(&repo_id, store.get_store_repo())?;
let _ = repo.opened_branches.insert(store_branch_id, true); let _ = repo.opened_branches.insert(store_branch_id, true);
} }

@ -25,6 +25,7 @@ use aes_gcm_siv::{
use argon2::{Algorithm, Argon2, AssociatedData, ParamsBuilder, Version}; use argon2::{Algorithm, Argon2, AssociatedData, ParamsBuilder, Version};
use chacha20poly1305::XChaCha20Poly1305; use chacha20poly1305::XChaCha20Poly1305;
use image::{imageops::FilterType, io::Reader as ImageReader, ImageOutputFormat}; use image::{imageops::FilterType, io::Reader as ImageReader, ImageOutputFormat};
use ng_net::types::Locator;
use rand::distributions::{Distribution, Uniform}; use rand::distributions::{Distribution, Uniform};
use rand::prelude::*; use rand::prelude::*;
use safe_transmute::transmute_to_bytes; use safe_transmute::transmute_to_bytes;
@ -650,8 +651,10 @@ pub async fn create_wallet_second_step_v0(
if let Some(additional) = &params.additional_bootstrap { if let Some(additional) = &params.additional_bootstrap {
params.core_bootstrap.merge(additional); params.core_bootstrap.merge(additional);
} }
let mut locator = Locator::empty();
for server in &params.core_bootstrap.servers { for server in &params.core_bootstrap.servers {
locator.add(server.clone());
wallet_log.add(WalletOperation::AddBrokerServerV0(server.clone())); wallet_log.add(WalletOperation::AddBrokerServerV0(server.clone()));
wallet_log.add(WalletOperation::AddSiteBootstrapV0((user, server.peer_id))); wallet_log.add(WalletOperation::AddSiteBootstrapV0((user, server.peer_id)));
site.bootstraps.push(server.peer_id); site.bootstraps.push(server.peer_id);
@ -666,6 +669,7 @@ pub async fn create_wallet_second_step_v0(
} }
list.unwrap().push(broker); list.unwrap().push(broker);
} }
verifier.update_locator(locator);
let mut master_key = [0u8; 32]; let mut master_key = [0u8; 32];
getrandom::getrandom(&mut master_key).map_err(|_e| NgWalletError::InternalError)?; getrandom::getrandom(&mut master_key).map_err(|_e| NgWalletError::InternalError)?;

@ -17,7 +17,7 @@ const config = {
plugins: [ plugins: [
require('flowbite/plugin') require('flowbite/plugin')
], ],
darkMode: 'class', darkMode: 'selector',
}; };
module.exports = config; module.exports = config;

@ -14,16 +14,19 @@ use std::collections::HashSet;
use std::error::Error; use std::error::Error;
use std::fs::{read_to_string, write}; use std::fs::{read_to_string, write};
use std::net::IpAddr; use std::net::IpAddr;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use clap::{arg, command, value_parser, Command}; use clap::{arg, command, value_parser, ArgMatches, Command};
use duration_str::parse; use duration_str::parse;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string_pretty}; use serde_json::{from_str, to_string_pretty};
use zeroize::Zeroize; use zeroize::Zeroize;
use ng_repo::errors::*; use ng_repo::errors::*;
use ng_repo::file::{FileError, RandomAccessFile, ReadFile};
use ng_repo::log::*; use ng_repo::log::*;
use ng_repo::object::Object; use ng_repo::object::Object;
use ng_repo::store::Store; use ng_repo::store::Store;
@ -76,6 +79,7 @@ pub enum NgcliError {
OtherConfigError(String), OtherConfigError(String),
OtherConfigErrorStr(&'static str), OtherConfigErrorStr(&'static str),
CannotSaveConfig(String), CannotSaveConfig(String),
FileError(FileError),
} }
impl Error for NgcliError {} impl Error for NgcliError {}
@ -97,6 +101,12 @@ impl From<NgError> for NgcliError {
} }
} }
impl From<FileError> for NgcliError {
fn from(err: FileError) -> NgcliError {
Self::FileError(err)
}
}
impl From<ProtocolError> for NgcliError { impl From<ProtocolError> for NgcliError {
fn from(err: ProtocolError) -> NgcliError { fn from(err: ProtocolError) -> NgcliError {
Self::ProtocolError(err) Self::ProtocolError(err)
@ -154,6 +164,73 @@ async fn main() -> std::io::Result<()> {
} }
Ok(()) Ok(())
} }
fn get_random_access_file(
file_meta: RandomAccessFileMeta,
reference: ObjectRef,
mut filename: Option<String>,
sub_matches: &ArgMatches,
store: &Arc<Store>,
) -> Result<(), NgcliError> {
println!("File content_type {}", file_meta.content_type());
println!("File size {}", file_meta.total_size());
let file = RandomAccessFile::open(reference.id, reference.key, Arc::clone(&store))?;
let filename_opt = sub_matches.get_one::<String>("output");
let save = sub_matches.get_flag("save") || filename_opt.is_some();
if save {
if filename.is_none() && filename_opt.is_some() {
filename = Some(filename_opt.unwrap().clone());
}
if let Some(filename) = filename {
let total = file_meta.total_size() as usize;
let mut file_content = Vec::with_capacity(total);
let mut pos = 0;
loop {
let mut res = file.read(pos, 1024 * 1024 * 1024)?;
pos += res.len();
file_content.append(&mut res);
if pos >= total {
break;
}
}
let mut i: usize = 0;
let mut dest_filename;
loop {
dest_filename = if i == 0 {
filename.clone()
} else {
filename
.rsplit_once(".")
.map(|(l, r)| format!("{l} ({}).{r}", i.to_string()))
.or_else(|| Some(format!("{filename} ({})", i.to_string())))
.unwrap()
};
let path = Path::new(&dest_filename);
if path.exists() {
i = i + 1;
} else {
write(path, &file_content)?;
break;
}
}
println!(
"The file has been saved in the current directory with the filename: {}",
dest_filename
);
} else {
println!("The file doesn't have a name and you didn't set the argument -o or --output , so we cannot save it.");
}
} else {
println!(
"You didn't set the argument -s or --save or -o or --output so we are not downloading the file locally"
);
}
Ok(())
}
async fn main_inner() -> Result<(), NgcliError> { async fn main_inner() -> Result<(), NgcliError> {
let matches = command!() let matches = command!()
.arg(arg!( .arg(arg!(
@ -237,6 +314,8 @@ async fn main_inner() -> Result<(), NgcliError> {
Command::new("get") Command::new("get")
.about("fetches one or several commits, or a binary object, with an optional signature, from a broker, using the Ext Protocol, and connecting on the Outer Overlay. The request is anonymous and doesn't need any authentication") .about("fetches one or several commits, or a binary object, with an optional signature, from a broker, using the Ext Protocol, and connecting on the Outer Overlay. The request is anonymous and doesn't need any authentication")
.arg(arg!([NURI] "NextGraph URI of the commit(s) or object, containing the ReadCap in the form :c:k :j:k and optionally :s:k and the usual :o:v:l").required(true)) .arg(arg!([NURI] "NextGraph URI of the commit(s) or object, containing the ReadCap in the form :c:k :j:k and optionally :s:k and the usual :o:v:l").required(true))
.arg(arg!(-s --save "Saves the binary file(s) of the commits that have the type AddFile").required(false))
.arg(arg!(-o --output <FILENAME> "Gives a filename for the binary file(s) to be saved locally. only used if no filename is present in metadata").required(false))
) )
.get_matches(); .get_matches();
@ -377,13 +456,41 @@ async fn main_inner() -> Result<(), NgcliError> {
let mut signature = None; let mut signature = None;
let arc_store = Arc::new(store);
for obj_ref in nuri.objects.into_iter() {
match Object::load(obj_ref.id, Some(obj_ref.key.clone()), &arc_store) {
Err(e) => println!("Error: {:?}", e),
Ok(o) => match o.content_v0() {
Err(e) => println!("Error: {:?}", e),
Ok(ObjectContentV0::Commit(c)) => {
next_round.push((c.body_ref().clone(), Some(obj_ref.id), c.files()));
}
Ok(ObjectContentV0::RandomAccessFileMeta(file_meta)) => {
if let Err(e) = get_random_access_file(
file_meta,
obj_ref,
None,
sub_matches,
&arc_store,
) {
println!("An error occurred: {:?}", e);
}
}
_ => println!("unsupported format"),
},
}
}
let store = Arc::try_unwrap(arc_store)
.map_err(|_| NgcliError::NgError(NgError::InternalError))?;
if let Some(sign_ref) = nuri.signature { if let Some(sign_ref) = nuri.signature {
if let Ok(o) = Object::load(sign_ref.id, Some(sign_ref.key), &store) { if let Ok(o) = Object::load(sign_ref.id, Some(sign_ref.key), &store) {
match o.content_v0() { match o.content_v0() {
Ok(ObjectContentV0::Signature(Signature::V0(v0))) => { Ok(ObjectContentV0::Signature(Signature::V0(v0))) => {
let in_sig = HashSet::from_iter(v0.content.commits().iter().cloned()); let in_sig = HashSet::from_iter(v0.content.commits().iter().cloned());
if in_nuri.is_subset(&in_sig) { if in_nuri.is_subset(&in_sig) {
next_round.push((v0.certificate_ref.clone(), None)); next_round.push((v0.certificate_ref.clone(), None, vec![]));
signature = Some(v0); signature = Some(v0);
} else { } else {
println!("Signature is invalid"); println!("Signature is invalid");
@ -394,20 +501,9 @@ async fn main_inner() -> Result<(), NgcliError> {
} }
} }
} }
if next_round.is_empty() {
nuri.objects.into_iter().for_each(|obj_ref| { return Ok(());
match Object::load(obj_ref.id, Some(obj_ref.key), &store) { }
Err(e) => println!("Error: {:?}", e),
Ok(o) => match o.content_v0() {
Err(e) => println!("Error: {:?}", e),
Ok(ObjectContentV0::Commit(c)) => {
next_round.push((c.body_ref().clone(), Some(obj_ref.id)));
}
_ => println!("unsupported format"),
},
}
});
let blocks: Vec<Block> = Broker::ext( let blocks: Vec<Block> = Broker::ext(
Box::new(ConnectionWebSocket {}), Box::new(ConnectionWebSocket {}),
peer_privk.clone(), peer_privk.clone(),
@ -418,7 +514,7 @@ async fn main_inner() -> Result<(), NgcliError> {
overlay: overlay_id, overlay: overlay_id,
ids: next_round ids: next_round
.iter() .iter()
.map(|(o, _)| o.id) .map(|(o, _, _)| o.id)
.collect::<Vec<ObjectId>>(), .collect::<Vec<ObjectId>>(),
include_files: true, include_files: true,
}, },
@ -428,7 +524,7 @@ async fn main_inner() -> Result<(), NgcliError> {
let mut third_round = Vec::with_capacity(2); let mut third_round = Vec::with_capacity(2);
let mut certificate = None; let mut certificate = None;
for (body_ref, commit_id) in next_round.into_iter() { for (body_ref, commit_id, mut files) in next_round.into_iter() {
match Object::load(body_ref.id, Some(body_ref.key), &store) { match Object::load(body_ref.id, Some(body_ref.key), &store) {
Err(e) => println!("Error: {:?}", e), Err(e) => println!("Error: {:?}", e),
Ok(o) => match o.content_v0() { Ok(o) => match o.content_v0() {
@ -437,16 +533,34 @@ async fn main_inner() -> Result<(), NgcliError> {
CommitBodyV0::Snapshot(Snapshot::V0(snap)), CommitBodyV0::Snapshot(Snapshot::V0(snap)),
))) => { ))) => {
println!("Snapshot: {}", commit_id.unwrap()); println!("Snapshot: {}", commit_id.unwrap());
third_round.push(snap.content); third_round.push((snap.content, None));
}
Ok(ObjectContentV0::CommitBody(CommitBody::V0(CommitBodyV0::AddFile(
AddFile::V0(add_file),
)))) => {
println!(
"File added: {}",
add_file.name.as_deref().unwrap_or("(no name)")
);
if files.len() != 1 {
println!("Error: invalid AddFile commit");
}
third_round.push((files.pop().unwrap(), add_file.name));
} }
Ok(ObjectContentV0::CommitBody(CommitBody::V0( Ok(ObjectContentV0::CommitBody(CommitBody::V0(
CommitBodyV0::AsyncTransaction(_t), CommitBodyV0::AsyncTransaction(t),
)))
| Ok(ObjectContentV0::CommitBody(CommitBody::V0(
CommitBodyV0::SyncTransaction(t),
))) => { ))) => {
println!("Transaction: {}", commit_id.unwrap()); println!("Transaction: {}", commit_id.unwrap());
if t.body_type() == 0 || t.body_type() == 2 {
//TODO display ADDs and REMOVEs
}
} }
Ok(ObjectContentV0::Certificate(Certificate::V0(certif))) => { Ok(ObjectContentV0::Certificate(Certificate::V0(certif))) => {
if commit_id.is_none() && signature.is_some() { if commit_id.is_none() && signature.is_some() {
third_round.push(certif.content.previous.clone()); third_round.push((certif.content.previous.clone(), None));
certificate = Some(certif); certificate = Some(certif);
} }
} }
@ -454,7 +568,9 @@ async fn main_inner() -> Result<(), NgcliError> {
}, },
} }
} }
if third_round.is_empty() {
return Ok(());
}
let blocks: Vec<Block> = Broker::ext( let blocks: Vec<Block> = Broker::ext(
Box::new(ConnectionWebSocket {}), Box::new(ConnectionWebSocket {}),
peer_privk, peer_privk,
@ -463,15 +579,18 @@ async fn main_inner() -> Result<(), NgcliError> {
broker_server.get_ws_url(&None).await.unwrap().0, // for now we are only connecting to NextGraph SaaS cloud (nextgraph.eu) so it is safe. broker_server.get_ws_url(&None).await.unwrap().0, // for now we are only connecting to NextGraph SaaS cloud (nextgraph.eu) so it is safe.
ExtObjectGetV0 { ExtObjectGetV0 {
overlay: overlay_id, overlay: overlay_id,
ids: third_round.iter().map(|o| o.id).collect::<Vec<ObjectId>>(), ids: third_round
.iter()
.map(|(o, _)| o.id)
.collect::<Vec<ObjectId>>(),
include_files: true, include_files: true,
}, },
) )
.await?; .await?;
blocks.into_iter().for_each(|b| _ = store.put(&b)); blocks.into_iter().for_each(|b| _ = store.put(&b));
let store = Arc::new(store);
for third_ref in third_round.into_iter() { for (third_ref, filename) in third_round.into_iter() {
match Object::load(third_ref.id, Some(third_ref.key), &store) { match Object::load(third_ref.id, Some(third_ref.key.clone()), &store) {
Err(e) => println!("Error: {:?}", e), Err(e) => println!("Error: {:?}", e),
Ok(o) => match o.content_v0() { Ok(o) => match o.content_v0() {
Err(e) => println!("Error: {:?}", e), Err(e) => println!("Error: {:?}", e),
@ -479,6 +598,15 @@ async fn main_inner() -> Result<(), NgcliError> {
Err(e) => println!("Error: {:?}", e), Err(e) => println!("Error: {:?}", e),
Ok(s) => println!("Here is the snapshot data :\n\r{}", s), Ok(s) => println!("Here is the snapshot data :\n\r{}", s),
}, },
Ok(ObjectContentV0::RandomAccessFileMeta(file_meta)) => {
get_random_access_file(
file_meta,
third_ref,
filename,
sub_matches,
&store,
)?;
}
Ok(ObjectContentV0::CommitBody(CommitBody::V0( Ok(ObjectContentV0::CommitBody(CommitBody::V0(
CommitBodyV0::Repository(repo), CommitBodyV0::Repository(repo),
))) => { ))) => {

Loading…
Cancel
Save