mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-03-07 01:21:09 +03:00
312 lines
8.4 KiB
Rust
312 lines
8.4 KiB
Rust
// Copyright 2020 The Grin Developers
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use crate::cmd::wallet_args;
|
|
use crate::util::secp::key::SecretKey;
|
|
use crate::util::Mutex;
|
|
use clap::App;
|
|
//use colored::Colorize;
|
|
use grin_wallet_api::Owner;
|
|
use grin_wallet_config::{TorConfig, WalletConfig};
|
|
use grin_wallet_controller::command::GlobalArgs;
|
|
use grin_wallet_controller::Error;
|
|
use grin_wallet_impls::DefaultWalletImpl;
|
|
use grin_wallet_libwallet::{NodeClient, StatusMessage, WalletInst, WalletLCProvider};
|
|
use grin_wallet_util::grin_keychain as keychain;
|
|
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
|
use rustyline::error::ReadlineError;
|
|
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
|
|
use rustyline::hint::Hinter;
|
|
use rustyline::validate::Validator;
|
|
use rustyline::{CompletionType, Config, Context, EditMode, Editor, Helper, OutputStreamType};
|
|
use std::borrow::Cow::{self, Borrowed, Owned};
|
|
use std::sync::mpsc::{channel, Receiver};
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
const COLORED_PROMPT: &'static str = "\x1b[36mgrin-wallet>\x1b[0m ";
|
|
const PROMPT: &'static str = "grin-wallet> ";
|
|
//const HISTORY_PATH: &str = ".history";
|
|
|
|
// static for keeping track of current stdin buffer contents
|
|
lazy_static! {
|
|
static ref STDIN_CONTENTS: Mutex<String> = Mutex::new(String::from(""));
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! cli_message_inline {
|
|
($fmt_string:expr, $( $arg:expr ),+) => {
|
|
{
|
|
use std::io::Write;
|
|
let contents = STDIN_CONTENTS.lock();
|
|
/* use crate::common::{is_cli, COLORED_PROMPT}; */
|
|
/* if is_cli() { */
|
|
print!("\r");
|
|
print!($fmt_string, $( $arg ),*);
|
|
print!(" {}", COLORED_PROMPT);
|
|
print!("\x1B[J");
|
|
print!("{}", *contents);
|
|
std::io::stdout().flush().unwrap();
|
|
/*} else {
|
|
info!($fmt_string, $( $arg ),*);
|
|
}*/
|
|
}
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! cli_message {
|
|
($fmt_string:expr, $( $arg:expr ),+) => {
|
|
{
|
|
use std::io::Write;
|
|
/* use crate::common::{is_cli, COLORED_PROMPT}; */
|
|
/* if is_cli() { */
|
|
//print!("\r");
|
|
print!($fmt_string, $( $arg ),*);
|
|
println!();
|
|
std::io::stdout().flush().unwrap();
|
|
/*} else {
|
|
info!($fmt_string, $( $arg ),*);
|
|
}*/
|
|
}
|
|
};
|
|
}
|
|
|
|
/// function to catch updates
|
|
pub fn start_updater_thread(rx: Receiver<StatusMessage>) -> Result<(), Error> {
|
|
let _ = thread::Builder::new()
|
|
.name("wallet-updater-status".to_string())
|
|
.spawn(move || loop {
|
|
while let Ok(m) = rx.recv() {
|
|
match m {
|
|
StatusMessage::UpdatingOutputs(s) => cli_message_inline!("{}", s),
|
|
StatusMessage::UpdatingTransactions(s) => cli_message_inline!("{}", s),
|
|
StatusMessage::FullScanWarn(s) => cli_message_inline!("{}", s),
|
|
StatusMessage::Scanning(_, m) => {
|
|
//debug!("{}", s);
|
|
cli_message_inline!("Scanning - {}% complete - Please Wait", m);
|
|
}
|
|
StatusMessage::ScanningComplete(s) => cli_message_inline!("{}", s),
|
|
StatusMessage::UpdateWarning(s) => cli_message_inline!("{}", s),
|
|
}
|
|
}
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
pub fn command_loop<L, C, K>(
|
|
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
|
|
keychain_mask: Option<SecretKey>,
|
|
wallet_config: &WalletConfig,
|
|
tor_config: &TorConfig,
|
|
global_wallet_args: &GlobalArgs,
|
|
test_mode: bool,
|
|
) -> Result<(), Error>
|
|
where
|
|
DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
|
|
L: WalletLCProvider<'static, C, K> + 'static,
|
|
C: NodeClient + 'static,
|
|
K: keychain::Keychain + 'static,
|
|
{
|
|
let editor = Config::builder()
|
|
.history_ignore_space(true)
|
|
.completion_type(CompletionType::List)
|
|
.edit_mode(EditMode::Emacs)
|
|
.output_stream(OutputStreamType::Stdout)
|
|
.build();
|
|
|
|
let mut reader = Editor::with_config(editor);
|
|
reader.set_helper(Some(EditorHelper(
|
|
FilenameCompleter::new(),
|
|
MatchingBracketHighlighter::new(),
|
|
)));
|
|
|
|
/*let history_file = self
|
|
.api
|
|
.config()
|
|
.get_data_path()
|
|
.unwrap()
|
|
.parent()
|
|
.unwrap()
|
|
.join(HISTORY_PATH);
|
|
if history_file.exists() {
|
|
let _ = reader.load_history(&history_file);
|
|
}*/
|
|
|
|
let yml = load_yaml!("../bin/grin-wallet.yml");
|
|
let mut app = App::from_yaml(yml).version(crate_version!());
|
|
let mut keychain_mask = keychain_mask;
|
|
|
|
// catch updater messages
|
|
let (tx, rx) = channel();
|
|
let mut owner_api = Owner::new(wallet_inst, Some(tx));
|
|
start_updater_thread(rx)?;
|
|
|
|
// start the automatic updater
|
|
owner_api.start_updater((&keychain_mask).as_ref(), Duration::from_secs(30))?;
|
|
let mut wallet_opened = false;
|
|
loop {
|
|
match reader.readline(PROMPT) {
|
|
Ok(command) => {
|
|
if command.is_empty() {
|
|
continue;
|
|
}
|
|
// TODO tidy up a bit
|
|
if command.to_lowercase() == "exit" {
|
|
break;
|
|
}
|
|
/* use crate::common::{is_cli, COLORED_PROMPT}; */
|
|
|
|
// reset buffer
|
|
{
|
|
let mut contents = STDIN_CONTENTS.lock();
|
|
*contents = String::from("");
|
|
}
|
|
|
|
// Just add 'grin-wallet' to each command behind the scenes
|
|
// so we don't need to maintain a separate definition file
|
|
let augmented_command = format!("grin-wallet {}", command);
|
|
let args =
|
|
app.get_matches_from_safe_borrow(augmented_command.trim().split_whitespace());
|
|
let done = match args {
|
|
Ok(args) => {
|
|
// handle opening /closing separately
|
|
keychain_mask = match args.subcommand() {
|
|
("open", Some(_)) => {
|
|
let mut wallet_lock = owner_api.wallet_inst.lock();
|
|
let lc = wallet_lock.lc_provider().unwrap();
|
|
let mask = match lc.open_wallet(
|
|
None,
|
|
wallet_args::prompt_password(&global_wallet_args.password),
|
|
false,
|
|
false,
|
|
) {
|
|
Ok(m) => {
|
|
wallet_opened = true;
|
|
m
|
|
}
|
|
Err(e) => {
|
|
cli_message!("{}", e);
|
|
None
|
|
}
|
|
};
|
|
if let Some(account) = args.value_of("account") {
|
|
if wallet_opened {
|
|
let wallet_inst = lc.wallet_inst()?;
|
|
wallet_inst.set_parent_key_id_by_name(account)?;
|
|
}
|
|
}
|
|
mask
|
|
}
|
|
("close", Some(_)) => {
|
|
let mut wallet_lock = owner_api.wallet_inst.lock();
|
|
let lc = wallet_lock.lc_provider().unwrap();
|
|
lc.close_wallet(None)?;
|
|
None
|
|
}
|
|
_ => keychain_mask,
|
|
};
|
|
match wallet_args::parse_and_execute(
|
|
&mut owner_api,
|
|
keychain_mask.clone(),
|
|
&wallet_config,
|
|
&tor_config,
|
|
&global_wallet_args,
|
|
&args,
|
|
test_mode,
|
|
true,
|
|
) {
|
|
Ok(_) => {
|
|
cli_message!("Command '{}' completed", args.subcommand().0);
|
|
false
|
|
}
|
|
Err(err) => {
|
|
cli_message!("{}", err);
|
|
false
|
|
}
|
|
}
|
|
}
|
|
Err(err) => {
|
|
cli_message!("{}", err);
|
|
false
|
|
}
|
|
};
|
|
reader.add_history_entry(command);
|
|
if done {
|
|
println!();
|
|
break;
|
|
}
|
|
}
|
|
Err(err) => {
|
|
println!("Unable to read line: {}", err);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
|
|
//let _ = reader.save_history(&history_file);
|
|
}
|
|
|
|
struct EditorHelper(FilenameCompleter, MatchingBracketHighlighter);
|
|
|
|
impl Completer for EditorHelper {
|
|
type Candidate = Pair;
|
|
|
|
fn complete(
|
|
&self,
|
|
line: &str,
|
|
pos: usize,
|
|
ctx: &Context<'_>,
|
|
) -> std::result::Result<(usize, Vec<Pair>), ReadlineError> {
|
|
self.0.complete(line, pos, ctx)
|
|
}
|
|
}
|
|
|
|
impl Hinter for EditorHelper {
|
|
fn hint(&self, line: &str, _pos: usize, _ctx: &Context<'_>) -> Option<String> {
|
|
let mut contents = STDIN_CONTENTS.lock();
|
|
*contents = line.into();
|
|
None
|
|
}
|
|
}
|
|
|
|
impl Highlighter for EditorHelper {
|
|
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
|
|
self.1.highlight(line, pos)
|
|
}
|
|
|
|
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
|
&'s self,
|
|
prompt: &'p str,
|
|
default: bool,
|
|
) -> Cow<'b, str> {
|
|
if default {
|
|
Borrowed(COLORED_PROMPT)
|
|
} else {
|
|
Borrowed(prompt)
|
|
}
|
|
}
|
|
|
|
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
|
Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
|
|
}
|
|
|
|
fn highlight_char(&self, line: &str, pos: usize) -> bool {
|
|
self.1.highlight_char(line, pos)
|
|
}
|
|
}
|
|
impl Validator for EditorHelper {}
|
|
impl Helper for EditorHelper {}
|