github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/cmd/rs-af/main.rs (about)

     1  extern crate chrono;
     2  #[macro_use] extern crate clap;
     3  extern crate cursive;
     4  extern crate reqwest;
     5  extern crate serde;
     6  #[macro_use] extern crate serde_derive;
     7  extern crate serde_json;
     8  
     9  use std::cmp;
    10  use std::panic;
    11  
    12  use chrono::prelude::*;
    13  
    14  use cursive::Cursive;
    15  use cursive::direction::*;
    16  use cursive::event::*;
    17  use cursive::theme::*;
    18  use cursive::view::*;
    19  use cursive::views::*;
    20  
    21  mod litrpc;
    22  
    23  fn main() {
    24  
    25      let matches = clap_app!(lit_af_rs =>
    26          (version: "0.1.0")
    27          (author: "Trey Del Bonis <j.delbonis.3@gmail.com>")
    28          (about: "CLI client for Lit")
    29          (@arg a: +takes_value "Address to connect to.  Default: localhost")
    30          (@arg p: +takes_value "Port to connect to lit to.  Default: idk yet lmao")
    31      ).get_matches(); // TODO Make these optional.
    32  
    33      let addr = matches.value_of("a").unwrap_or("localhost");
    34      let port = match matches.value_of("p").map(str::parse) {
    35          Some(Ok(p)) => p,
    36          Some(Err(_)) => panic!("port is not a number"),
    37          None => 12345 // FIXME
    38      };
    39  
    40      println!("addr: {}, port {}", addr, port);
    41  
    42      match panic::catch_unwind(|| run_ui(addr, port)) {
    43          Ok(_) => {}, // we're ok
    44          Err(_) => run_bsod()
    45      }
    46  
    47  }
    48  
    49  fn run_ui(addr: &str, port: u16) {
    50  
    51      let mut client = Box::new(litrpc::LitRpcClient::new(addr, port));
    52  
    53      let mut layout = LinearLayout::new(Orientation::Horizontal);
    54  
    55      let mut c_view = LinearLayout::new(Orientation::Vertical);
    56      c_view.add_child(TextView::new("Channels"));
    57      layout.add_child(
    58          BoxView::new(
    59              SizeConstraint::Full,
    60              SizeConstraint::Full,
    61              IdView::new("chans", Panel::new(c_view))));
    62  
    63      let mut right_view = LinearLayout::new(Orientation::Vertical);
    64  
    65      // Balances
    66      let mut bal_view = LinearLayout::new(Orientation::Vertical);
    67      bal_view.add_child(TextView::new("Balances"));
    68      right_view.add_child(
    69          BoxView::new(
    70                  SizeConstraint::Full,
    71                  SizeConstraint::Full,
    72                  IdView::new("bals", Panel::new(bal_view)))
    73              .squishable());
    74  
    75      // Txos
    76      let mut txo_view = LinearLayout::new(Orientation::Vertical);
    77      txo_view.add_child(TextView::new("Txos"));
    78      right_view.add_child(
    79          BoxView::new(
    80                  SizeConstraint::Full,
    81                  SizeConstraint::Full,
    82                  IdView::new("txos", Panel::new(txo_view)))
    83              .squishable());
    84  
    85      layout.add_child(BoxView::new(SizeConstraint::Full, SizeConstraint::Full, right_view));
    86  
    87      let mut siv = Cursive::new();
    88      siv.add_layer(BoxView::new(SizeConstraint::Full, SizeConstraint::Full, layout));
    89  
    90      siv.set_theme(load_theme(include_str!("ncurses_theme.toml")).unwrap());
    91      siv.add_global_callback(Event::Refresh, make_update_ui_callback_with_client(&mut client));
    92      siv.set_fps(1);
    93  
    94      siv.run()
    95  
    96  }
    97  
    98  fn run_bsod() {
    99  
   100      let mut siv = Cursive::new();
   101  
   102      let d = Dialog::around(TextView::new("RS-AF has encountered an error and needs to exit."))
   103                      .title("Panic")
   104                      .button("Exit", |s| s.quit());
   105  
   106      siv.add_layer(d);
   107      siv.run();
   108  
   109  }
   110  
   111  fn generate_view_for_chan(chan: litrpc::ChanInfo) -> impl View {
   112  
   113      let ndt = NaiveDateTime::from_timestamp(
   114          (chan.LastUpdate / 1000) as i64,
   115          ((chan.LastUpdate % 1000) * 1000) as u32);
   116      let dt: DateTime<Utc> = DateTime::from_utc(ndt, chrono::Utc);
   117  
   118      let mut data = LinearLayout::new(Orientation::Vertical);
   119      data.add_child(TextView::new(format!("Channel # {}", chan.CIdx)));
   120      data.add_child(TextView::new(format!("Outpoint: {}", chan.OutPoint)));
   121      data.add_child(TextView::new(format!("Peer: {}", chan.PeerIdx)));
   122      data.add_child(TextView::new(format!("Coin Type: {}", chan.CoinType)));
   123      data.add_child(TextView::new(format!("Last Activity: {}", dt.to_rfc3339())));
   124      data.add_child(DummyView);
   125  
   126      data.add_child(TextView::new(format!("Balance: {}/{}", chan.MyBalance, chan.Capacity)));
   127      let mut bar = ProgressBar::new().range(0, chan.Capacity as usize);
   128      bar.set_value(chan.MyBalance as usize);
   129      data.add_child(bar);
   130  
   131      let cbox = BoxView::new(SizeConstraint::Full, SizeConstraint::AtLeast(5), data);
   132      Panel::new(cbox)
   133  
   134  }
   135  
   136  fn generate_view_for_bal(bal: &litrpc::CoinBalInfo, addrs: Vec<String>) -> impl View {
   137  
   138      let mut data = LinearLayout::new(Orientation::Vertical);
   139  
   140      let grand_total = bal.ChanTotal + bal.TxoTotal;
   141      let bal_str = format!(
   142          "  Funds: chans {} + txos {} = total {} (sat)",
   143          bal.ChanTotal,
   144          bal.TxoTotal,
   145          grand_total);
   146  
   147      data.add_child(TextView::new(format!("- Type {} @ height {}", bal.CoinType, bal.SyncHeight)));
   148      data.add_child(TextView::new(bal_str));
   149      addrs.into_iter()
   150          .map(|a| format!("  - {}", a))
   151          .map(TextView::new)
   152          .for_each(|l| data.add_child(l));
   153      data.add_child(DummyView);
   154  
   155      data
   156  
   157  }
   158  
   159  fn generate_view_for_txo(txo: litrpc::TxoInfo) -> impl View {
   160  
   161      // TODO Make this prettier
   162      let strs = vec![
   163          ("Outpoint", txo.OutPoint),
   164          ("Amount", format!("{}", txo.Amt)),
   165          ("Height", format!("{}", txo.Height)),
   166          ("Coin Type", txo.CoinType),
   167          ("Key Path", txo.KeyPath)
   168      ];
   169  
   170      let mut data = LinearLayout::new(Orientation::Vertical);
   171      for (k, v) in strs {
   172          data.add_child(TextView::new(format!("{}: {}", k, v)));
   173      }
   174  
   175      let cbox = BoxView::new(SizeConstraint::Full, SizeConstraint::Free, data);
   176      Panel::new(cbox)
   177  
   178  }
   179  
   180  fn make_update_ui_callback_with_client(cl: &mut litrpc::LitRpcClient) -> impl Fn(&mut Cursive) {
   181  
   182      use std::mem;
   183  
   184      let clp: *mut litrpc::LitRpcClient = unsafe { mem::transmute(cl) };
   185  
   186      move |c: &mut Cursive| {
   187  
   188          let clrc: &mut litrpc::LitRpcClient = unsafe { mem::transmute(clp) };
   189  
   190          // Channels.
   191          let chans: Vec<litrpc::ChanInfo> = match clrc.call_chan_list(0) {
   192              Ok(clr) => {
   193                  let mut cls = clr.Channels;
   194                  // Reversed because we want the newest at the top.
   195                  cls.sort_by(|a, b| cmp::Ord::cmp(&b.LastUpdate, &a.LastUpdate));
   196                  cls
   197              },
   198              Err(err) => panic!("{:?}", err)
   199          };
   200  
   201          c.call_on_id("chans", |cpan: &mut Panel<LinearLayout>| {
   202  
   203              let mut c_view = LinearLayout::new(Orientation::Vertical);
   204              c_view.add_child(TextView::new("Channels"));
   205              chans.into_iter()
   206                  .map(generate_view_for_chan)
   207                  .for_each(|e| c_view.add_child(e));
   208  
   209              *cpan.get_inner_mut() = c_view;
   210  
   211          });
   212  
   213          // Bals
   214          let bals: Vec<litrpc::CoinBalInfo> = match clrc.call_bal() {
   215              Ok(br) => {
   216                  let mut bals = br.Balances;
   217                  bals.sort_by(|a, b| cmp::Ord::cmp(&a.CoinType, &b.CoinType));
   218                  bals
   219              },
   220              Err(err) => panic!("{:?}", err)
   221          };
   222  
   223          // Addrs
   224          let addrs: Vec<(u32, String)> = match clrc.call_get_addresses() {
   225              Ok(ar) => {
   226                  let mut addrs: Vec<(u32, String)> = ar.CoinTypes.into_iter()
   227                      .zip(ar.WitAddresses.into_iter())
   228                      .collect();
   229                  addrs.sort_by(|a, b| match cmp::Ord::cmp(&a.0, &b.0) {
   230                      cmp::Ordering::Equal => cmp::Ord::cmp(&a.1, &b.1),
   231                      o => o
   232                  });
   233                  addrs
   234              },
   235              Err(err) => panic!("{:?}", err)
   236          };
   237  
   238          c.call_on_id("bals", |balpan: &mut Panel<LinearLayout>| {
   239  
   240              let mut bal_view = LinearLayout::new(Orientation::Vertical);
   241              bal_view.add_child(TextView::new("Balances"));
   242              bal_view.add_child(DummyView);
   243              bals.into_iter()
   244                  .map(|b| generate_view_for_bal(
   245                      &b,
   246                      addrs.clone().into_iter()
   247                          .filter(|(t, _)| *t == b.CoinType)
   248                          .map(|(_, a)| a)
   249                          .collect()))
   250                  .for_each(|e| bal_view.add_child(e));
   251  
   252              *balpan.get_inner_mut() = bal_view;
   253  
   254          });
   255  
   256          // Txos
   257          let txos: Vec<litrpc::TxoInfo> = match clrc.call_get_txo_list() {
   258              Ok(txr) => txr.Txos,
   259              Err(err) => {
   260                  eprintln!("error: {:?}", err);
   261                  Vec::new()
   262              }
   263          };
   264  
   265          c.call_on_id("txos", |txopan: &mut Panel<LinearLayout>| {
   266  
   267              let mut txo_view = LinearLayout::new(Orientation::Vertical);
   268              txo_view.add_child(TextView::new("Txos"));
   269              txos.into_iter()
   270                  .map(generate_view_for_txo)
   271                  .for_each(|e| txo_view.add_child(e));
   272  
   273              *txopan.get_inner_mut() = txo_view;
   274  
   275          });
   276  
   277      }
   278  }