diff --git a/src/commands.rs b/src/commands.rs index 4c090a7..e7e627f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use json::{object}; use crate::LightClient; @@ -311,6 +312,31 @@ impl Command for TransactionsCommand { } } +struct HeightCommand {} +impl Command for HeightCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Get the latest block height that the wallet is at"); + h.push("Usage:"); + h.push("height"); + h.push(""); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Get the latest block height that the wallet is at".to_string() + } + + fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { + format!("{}", + object! { + "height" => lightclient.last_scanned_height() + }.pretty(2)) + } +} + + struct NewAddressCommand {} impl Command for NewAddressCommand { fn help(&self) -> String { @@ -407,6 +433,7 @@ pub fn get_commands() -> Box>> { map.insert("help".to_string(), Box::new(HelpCommand{})); map.insert("balance".to_string(), Box::new(BalanceCommand{})); map.insert("addresses".to_string(), Box::new(AddressCommand{})); + map.insert("height".to_string(), Box::new(HeightCommand{})); map.insert("export".to_string(), Box::new(ExportCommand{})); map.insert("info".to_string(), Box::new(InfoCommand{})); map.insert("send".to_string(), Box::new(SendCommand{})); diff --git a/src/lightclient.rs b/src/lightclient.rs index d9d7295..7e8793b 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -1,7 +1,6 @@ use crate::lightwallet::LightWallet; use log::{info, warn, error}; - use rand::{rngs::OsRng, seq::SliceRandom}; use std::sync::{Arc, RwLock}; @@ -328,7 +327,18 @@ impl LightClient { pub fn do_info(&self) -> String { match get_info(self.get_server_uri(), self.config.no_cert_verification) { - Ok(i) => format!("{:?}", i)[11..].to_string(), + Ok(i) => { + let o = object!{ + "version" => i.version, + "vendor" => i.vendor, + "taddr_support" => i.taddr_support, + "chain_name" => i.chain_name, + "sapling_activation_height" => i.sapling_activation_height, + "consensus_branch_id" => i.consensus_branch_id, + "latest_block_height" => i.block_height + }; + o.pretty(2) + }, Err(e) => e } } diff --git a/src/main.rs b/src/main.rs index 5ff7bbf..c8483f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -129,57 +129,70 @@ pub fn main() { } let dangerous = matches.is_present("dangerous"); + let nosync = matches.is_present("nosync"); - // Try to get the configuration - let (config, latest_block_height) = match create_lightclient_config(server.clone(), dangerous) { - Ok((c, h)) => (c, h), - Err(e) => { - eprintln!("Couldn't create config: {}", e); - return; - } - }; - - // Configure logging first. - let log_config = match get_log_config(&config) { + let (command_tx, resp_rx) = match startup(server, dangerous, seed, !nosync, command.is_none()) { Ok(c) => c, Err(e) => { - eprintln!("Error:\n{}\nCouldn't configure logging, quitting!", e); + eprintln!("Error during startup: {}", e); + error!("Error during startup: {}", e); return; } }; - log4rs::init_config(log_config).unwrap(); - let lightclient = match create_lightclient(seed, latest_block_height, &config) { - Ok(lc) => Arc::new(lc), - Err(e) => { - eprintln!("Couldn't create Lightclient. {}", e); - error!("Couldn't create Lightclient. {}", e); - return; + if command.is_none() { + start_interactive(command_tx, resp_rx); + } else { + command_tx.send( + (command.unwrap().to_string(), + params.iter().map(|s| s.to_string()).collect::>())) + .unwrap(); + + match resp_rx.recv() { + Ok(s) => println!("{}", s), + Err(e) => { + let e = format!("Error executing command {}: {}", command.unwrap(), e); + eprintln!("{}", e); + error!("{}", e); + } } - }; + } +} + +fn startup(server: http::Uri, dangerous: bool, seed: Option, first_sync: bool, print_updates: bool) + -> Result<(Sender<(String, Vec)>, Receiver)> { + // Try to get the configuration + let (config, latest_block_height) = create_lightclient_config(server.clone(), dangerous)?; + + // Configure logging first. + let log_config = get_log_config(&config)?; + log4rs::init_config(log_config).map_err(|e| { + std::io::Error::new(ErrorKind::Other, e) + })?; - // Startup + let lightclient = Arc::new(create_lightclient(seed, latest_block_height, &config)?); + + // Print startup Messages info!(""); // Blank line info!("Starting Zecwallet-CLI"); info!("Light Client config {:?}", config); - // At startup, run a sync. - let sync_output = if matches.is_present("nosync") { - None - } else { - Some(lightclient.do_sync(true)) - }; + if print_updates { + println!("Lightclient connecting to {}", config.server); + } - if command.is_none() { - // If running in interactive mode, output of the sync command - if sync_output.is_some() { - println!("{}", sync_output.unwrap()); + // Start the command loop + let (command_tx, resp_rx) = command_loop(lightclient.clone()); + + // At startup, run a sync. + if first_sync { + let update = lightclient.do_sync(true); + if print_updates { + println!("{}", update); } - start_interactive(lightclient, &config); - } else { - let cmd_response = commands::do_user_command(&command.unwrap(), ¶ms, lightclient.as_ref()); - println!("{}", cmd_response); } + + Ok((command_tx, resp_rx)) } fn create_lightclient_config(server: http::Uri, dangerous: bool) -> Result<(LightClientConfig, u64)> { @@ -206,19 +219,34 @@ fn create_lightclient(seed: Option, latest_block: u64, config: &LightCli Ok(lightclient) } -fn start_interactive(lightclient: Arc, config: &LightClientConfig) { - // Start the command loop - let (command_tx, resp_rx) = command_loop(lightclient.clone(), config); - +fn start_interactive(command_tx: Sender<(String, Vec)>, resp_rx: Receiver) { // `()` can be used when no completer is required let mut rl = Editor::<()>::new(); println!("Ready!"); + let send_command = |cmd: String, args: Vec| -> String { + command_tx.send((cmd.clone(), args)).unwrap(); + match resp_rx.recv() { + Ok(s) => s, + Err(e) => { + let e = format!("Error executing command {}: {}", cmd, e); + eprintln!("{}", e); + error!("{}", e); + return "".to_string() + } + } + }; + + let info = &send_command("info".to_string(), vec![]); + let chain_name = json::parse(info).unwrap()["chain_name"].as_str().unwrap().to_string(); + loop { + // Read the height first + let height = json::parse(&send_command("height".to_string(), vec![])).unwrap()["height"].as_i64().unwrap(); + let readline = rl.readline(&format!("({}) Block:{} (type 'help') >> ", - config.chain_name, - lightclient.last_scanned_height())); + chain_name, height)); match readline { Ok(line) => { rl.add_history_entry(line.as_str()); @@ -236,14 +264,9 @@ fn start_interactive(lightclient: Arc, config: &LightClientConfig) } let cmd = cmd_args.remove(0); - let args: Vec = cmd_args; - command_tx.send((cmd, args)).unwrap(); + let args: Vec = cmd_args; - // Wait for the response - match resp_rx.recv() { - Ok(response) => println!("{}", response), - _ => { eprintln!("Error receiving response");} - } + println!("{}", send_command(cmd, args)); // Special check for Quit command. if line == "quit" { @@ -253,13 +276,13 @@ fn start_interactive(lightclient: Arc, config: &LightClientConfig) Err(ReadlineError::Interrupted) => { println!("CTRL-C"); info!("CTRL-C"); - println!("{}", lightclient.do_save()); + println!("{}", send_command("save".to_string(), vec![])); break }, Err(ReadlineError::Eof) => { println!("CTRL-D"); info!("CTRL-D"); - println!("{}", lightclient.do_save()); + println!("{}", send_command("save".to_string(), vec![])); break }, Err(err) => { @@ -271,9 +294,7 @@ fn start_interactive(lightclient: Arc, config: &LightClientConfig) } -fn command_loop(lightclient: Arc, config: &LightClientConfig) -> (Sender<(String, Vec)>, Receiver) { - println!("Lightclient connecting to {}", config.server); - +fn command_loop(lightclient: Arc) -> (Sender<(String, Vec)>, Receiver) { let (command_tx, command_rx) = channel::<(String, Vec)>(); let (resp_tx, resp_rx) = channel::();