1use crate::cmd::wallet_args;
16use crate::util::secp::key::SecretKey;
17use crate::util::Mutex;
18use clap::App;
19use grin_keychain as keychain;
21use grin_wallet_api::Owner;
22use grin_wallet_config::{TorConfig, WalletConfig};
23use grin_wallet_controller::command::GlobalArgs;
24use grin_wallet_controller::Error;
25use grin_wallet_impls::DefaultWalletImpl;
26use grin_wallet_libwallet::{NodeClient, StatusMessage, WalletInst, WalletLCProvider};
27use rustyline::completion::{Completer, FilenameCompleter, Pair};
28use rustyline::error::ReadlineError;
29use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
30use rustyline::hint::Hinter;
31use rustyline::validate::Validator;
32use rustyline::{CompletionType, Config, Context, EditMode, Editor, Helper, OutputStreamType};
33use std::borrow::Cow::{self, Borrowed, Owned};
34use std::sync::mpsc::{channel, Receiver};
35use std::sync::Arc;
36use std::thread;
37use std::time::Duration;
38
39const COLORED_PROMPT: &'static str = "\x1b[36mgrin-wallet>\x1b[0m ";
40const PROMPT: &'static str = "grin-wallet> ";
41lazy_static! {
45 static ref STDIN_CONTENTS: Mutex<String> = Mutex::new(String::from(""));
46}
47
48#[macro_export]
49macro_rules! cli_message_inline {
50 ($fmt_string:expr, $( $arg:expr ),+) => {
51 {
52 use std::io::Write;
53 let contents = STDIN_CONTENTS.lock();
54 print!("\r");
57 print!($fmt_string, $( $arg ),*);
58 print!(" {}", COLORED_PROMPT);
59 print!("\x1B[J");
60 print!("{}", *contents);
61 std::io::stdout().flush().unwrap();
62 }
66 };
67}
68
69#[macro_export]
70macro_rules! cli_message {
71 ($fmt_string:expr, $( $arg:expr ),+) => {
72 {
73 use std::io::Write;
74 print!($fmt_string, $( $arg ),*);
78 println!();
79 std::io::stdout().flush().unwrap();
80 }
84 };
85}
86
87pub fn start_updater_thread(rx: Receiver<StatusMessage>) -> Result<(), Error> {
89 let _ = thread::Builder::new()
90 .name("wallet-updater-status".to_string())
91 .spawn(move || loop {
92 while let Ok(m) = rx.recv() {
93 match m {
94 StatusMessage::UpdatingOutputs(s) => cli_message_inline!("{}", s),
95 StatusMessage::UpdatingTransactions(s) => cli_message_inline!("{}", s),
96 StatusMessage::FullScanWarn(s) => cli_message_inline!("{}", s),
97 StatusMessage::Scanning(_, m) => {
98 cli_message_inline!("Scanning - {}% complete - Please Wait", m);
100 }
101 StatusMessage::ScanningComplete(s) => cli_message_inline!("{}", s),
102 StatusMessage::UpdateWarning(s) => cli_message_inline!("{}", s),
103 }
104 }
105 });
106 Ok(())
107}
108
109pub fn command_loop<L, C, K>(
110 wallet_inst: Arc<Mutex<Box<dyn WalletInst<'static, L, C, K>>>>,
111 keychain_mask: Option<SecretKey>,
112 wallet_config: &WalletConfig,
113 tor_config: &TorConfig,
114 global_wallet_args: &GlobalArgs,
115 test_mode: bool,
116) -> Result<(), Error>
117where
118 DefaultWalletImpl<'static, C>: WalletInst<'static, L, C, K>,
119 L: WalletLCProvider<'static, C, K> + 'static,
120 C: NodeClient + 'static,
121 K: keychain::Keychain + 'static,
122{
123 let editor = Config::builder()
124 .history_ignore_space(true)
125 .completion_type(CompletionType::List)
126 .edit_mode(EditMode::Emacs)
127 .output_stream(OutputStreamType::Stdout)
128 .build();
129
130 let mut reader = Editor::with_config(editor);
131 reader.set_helper(Some(EditorHelper(
132 FilenameCompleter::new(),
133 MatchingBracketHighlighter::new(),
134 )));
135
136 let yml = load_yaml!("../bin/grin-wallet.yml");
149 let mut app = App::from_yaml(yml).version(crate_version!());
150 let mut keychain_mask = keychain_mask;
151
152 let (tx, rx) = channel();
154 let mut owner_api = Owner::new(wallet_inst, Some(tx));
155 start_updater_thread(rx)?;
156
157 owner_api.start_updater((&keychain_mask).as_ref(), Duration::from_secs(30))?;
159 let mut wallet_opened = false;
160 loop {
161 match reader.readline(PROMPT) {
162 Ok(command) => {
163 if command.is_empty() {
164 continue;
165 }
166 if command.to_lowercase() == "exit" {
168 break;
169 }
170 {
174 let mut contents = STDIN_CONTENTS.lock();
175 *contents = String::from("");
176 }
177
178 let augmented_command = format!("grin-wallet {}", command);
181 let args =
182 app.get_matches_from_safe_borrow(augmented_command.trim().split_whitespace());
183 let done = match args {
184 Ok(args) => {
185 keychain_mask = match args.subcommand() {
187 ("open", Some(_)) => {
188 let mut wallet_lock = owner_api.wallet_inst.lock();
189 let lc = wallet_lock.lc_provider().unwrap();
190 let mask = match lc.open_wallet(
191 None,
192 wallet_args::prompt_password(&global_wallet_args.password),
193 false,
194 false,
195 ) {
196 Ok(m) => {
197 wallet_opened = true;
198 m
199 }
200 Err(e) => {
201 cli_message!("{}", e);
202 None
203 }
204 };
205 if let Some(account) = args.value_of("account") {
206 if wallet_opened {
207 let wallet_inst = lc.wallet_inst()?;
208 wallet_inst.set_parent_key_id_by_name(account)?;
209 }
210 }
211 mask
212 }
213 ("close", Some(_)) => {
214 let mut wallet_lock = owner_api.wallet_inst.lock();
215 let lc = wallet_lock.lc_provider().unwrap();
216 lc.close_wallet(None)?;
217 None
218 }
219 _ => keychain_mask,
220 };
221 match wallet_args::parse_and_execute(
222 &mut owner_api,
223 keychain_mask.clone(),
224 &wallet_config,
225 &tor_config,
226 &global_wallet_args,
227 &args,
228 test_mode,
229 true,
230 ) {
231 Ok(_) => {
232 cli_message!("Command '{}' completed", args.subcommand().0);
233 false
234 }
235 Err(err) => {
236 cli_message!("{}", err);
237 false
238 }
239 }
240 }
241 Err(err) => {
242 cli_message!("{}", err);
243 false
244 }
245 };
246 reader.add_history_entry(command);
247 if done {
248 println!();
249 break;
250 }
251 }
252 Err(err) => {
253 println!("Unable to read line: {}", err);
254 break;
255 }
256 }
257 }
258 Ok(())
259
260 }
262
263struct EditorHelper(FilenameCompleter, MatchingBracketHighlighter);
264
265impl Completer for EditorHelper {
266 type Candidate = Pair;
267
268 fn complete(
269 &self,
270 line: &str,
271 pos: usize,
272 ctx: &Context<'_>,
273 ) -> std::result::Result<(usize, Vec<Pair>), ReadlineError> {
274 self.0.complete(line, pos, ctx)
275 }
276}
277
278impl Hinter for EditorHelper {
279 fn hint(&self, line: &str, _pos: usize, _ctx: &Context<'_>) -> Option<String> {
280 let mut contents = STDIN_CONTENTS.lock();
281 *contents = line.into();
282 None
283 }
284}
285
286impl Highlighter for EditorHelper {
287 fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
288 self.1.highlight(line, pos)
289 }
290
291 fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
292 &'s self,
293 prompt: &'p str,
294 default: bool,
295 ) -> Cow<'b, str> {
296 if default {
297 Borrowed(COLORED_PROMPT)
298 } else {
299 Borrowed(prompt)
300 }
301 }
302
303 fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
304 Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
305 }
306
307 fn highlight_char(&self, line: &str, pos: usize) -> bool {
308 self.1.highlight_char(line, pos)
309 }
310}
311impl Validator for EditorHelper {}
312impl Helper for EditorHelper {}