Skip to content

Commit

Permalink
Work asyncronously with apis
Browse files Browse the repository at this point in the history
  • Loading branch information
mertwole committed Oct 16, 2024
1 parent 32018de commit fe8a248
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 89 deletions.
4 changes: 2 additions & 2 deletions app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ impl App {
//let mut ledger_api = block_on(LedgerApiCache::new(ledger_api));
//ledger_api.set_all_modes(ModePlan::Transparent);

let _coin_price_api = CoinPriceApiMock::new();
let coin_price_api = CoinPriceApi::new("https://data-api.binance.vision");
let coin_price_api = CoinPriceApiMock::new();
//let coin_price_api = CoinPriceApi::new("https://data-api.binance.vision");
//let mut coin_price_api = block_on(CoinPriceApiCache::new(coin_price_api));
//coin_price_api.set_all_modes(ModePlan::Slow(Duration::from_secs(1)));

Expand Down
70 changes: 40 additions & 30 deletions app/src/screen/asset/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::sync::Arc;
use std::sync::{Arc, Mutex};

use futures::executor::block_on;
use ratatui::{crossterm::event::Event, Frame};
use rust_decimal::Decimal;
use strum::EnumIter;
Expand All @@ -22,8 +21,8 @@ mod view;
const DEFAULT_SELECTED_TIME_PERIOD: TimePeriod = TimePeriod::Day;

pub struct Model<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> {
coin_price_history: Option<Vec<PriceHistoryPoint>>,
transactions: Option<Vec<(TransactionUid, TransactionInfo)>>,
coin_price_history: Arc<Mutex<Option<Vec<PriceHistoryPoint>>>>,
transactions: Arc<Mutex<Option<Vec<(TransactionUid, TransactionInfo)>>>>,
selected_time_period: TimePeriod,
show_navigation_help: bool,

Expand All @@ -48,7 +47,8 @@ impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> Model<L, C, M
.state
.selected_account
.as_ref()
.expect("Selected account should be present in state"); // TODO: Enforce this rule at `app` level?
.expect("Selected account should be present in state")
.clone(); // TODO: Enforce this rule at `app` level?

let coin = match selected_network {
Network::Bitcoin => Coin::BTC,
Expand All @@ -63,33 +63,43 @@ impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> Model<L, C, M
TimePeriod::All => ApiTimePeriod::All,
};

self.coin_price_history = block_on(self.apis.coin_price_api.get_price_history(
coin,
Coin::USDT,
time_period,
));
let apis = self.apis.clone();
let coin_price_history = self.coin_price_history.clone();

// TODO: Don't make requests to API each tick.
let tx_list = block_on(
self.apis
tokio::task::spawn(async move {
let price_history = apis
.coin_price_api
.get_price_history(coin, Coin::USDT, time_period)
.await;

*coin_price_history
.lock()
.expect("Failed to acquire lock on mutex") = price_history;
});

let apis = self.apis.clone();
let transactions = self.transactions.clone();

tokio::task::spawn(async move {
let tx_list = apis
.blockchain_monitoring_api
.get_transactions(*selected_network, selected_account),
);
let txs = tx_list
.into_iter()
.map(|tx_uid| {
(
tx_uid.clone(),
block_on(
self.apis
.blockchain_monitoring_api
.get_transaction_info(*selected_network, &tx_uid),
),
)
})
.collect();

self.transactions = Some(txs);
.get_transactions(selected_network, &selected_account)
.await;

let mut txs = vec![];
for tx in tx_list {
let tx_info = apis
.blockchain_monitoring_api
.get_transaction_info(selected_network, &tx)
.await;

txs.push((tx, tx_info));
}

*transactions
.lock()
.expect("Failed to acquire lock on mutex") = Some(txs);
});
}
}

Expand Down
14 changes: 12 additions & 2 deletions app/src/screen/asset/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
let inner_price_chart_area = price_chart_block.inner(price_chart_area);
frame.render_widget(price_chart_block, price_chart_area);

if let Some(prices) = model.coin_price_history.as_ref() {
if let Some(prices) = model
.coin_price_history
.lock()
.expect("Failed to acquire lock on mutex")
.as_ref()
{
render_price_chart(
&prices[..],
model.selected_time_period,
Expand All @@ -81,7 +86,12 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
let inner_txs_list_area = txs_list_block.inner(txs_list_area);
frame.render_widget(txs_list_block, txs_list_area);

match model.transactions.as_ref() {
match model
.transactions
.lock()
.expect("Failed to acquire lock on mutex")
.as_ref()
{
Some(tx_list) if tx_list.is_empty() => {
render_empty_tx_list(frame, inner_txs_list_area);
}
Expand Down
15 changes: 10 additions & 5 deletions app/src/screen/device_selection/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,21 @@ pub(super) fn process_input<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonito
) -> Option<OutgoingMessage> {
let event = InputEvent::map_event(event.clone())?;

let devices = model
.devices
.lock()
.expect("Failed to acquire lock on mutex");

match event {
InputEvent::Quit => Some(OutgoingMessage::Exit),
InputEvent::NavigationHelp => {
model.show_navigation_help ^= true;
None
}
InputEvent::Down => {
if !model.devices.is_empty() {
if !devices.is_empty() {
if let Some(selected) = model.selected_device.as_mut() {
*selected = (model.devices.len() - 1).min(*selected + 1);
*selected = (devices.len() - 1).min(*selected + 1);
} else {
model.selected_device = Some(0);
}
Expand All @@ -59,19 +64,19 @@ pub(super) fn process_input<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonito
None
}
InputEvent::Up => {
if !model.devices.is_empty() {
if !devices.is_empty() {
if let Some(selected) = model.selected_device.as_mut() {
*selected = if *selected == 0 { 0 } else { *selected - 1 };
} else {
model.selected_device = Some(model.devices.len() - 1);
model.selected_device = Some(devices.len() - 1);
}
}

None
}
InputEvent::Select => {
if let Some(device_idx) = model.selected_device {
let (device, info) = model.devices[device_idx].clone();
let (device, info) = devices[device_idx].clone();
model.state.active_device = Some((device, info));

Some(OutgoingMessage::Back)
Expand Down
40 changes: 20 additions & 20 deletions app/src/screen/device_selection/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
use std::{
sync::Arc,
time::{Duration, Instant},
};
use std::sync::{Arc, Mutex};

use futures::executor::block_on;
use ratatui::{crossterm::event::Event, Frame};

use super::{resources::Resources, OutgoingMessage, ScreenT};
Expand All @@ -19,11 +15,8 @@ use crate::{
mod controller;
mod view;

const DEVICE_POLL_PERIOD: Duration = Duration::from_secs(2);

pub struct Model<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> {
devices: Vec<(Device, DeviceInfo)>,
previous_poll: Instant,
devices: Arc<Mutex<Vec<(Device, DeviceInfo)>>>,
selected_device: Option<usize>,
show_navigation_help: bool,

Expand All @@ -33,29 +26,37 @@ pub struct Model<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> {

impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> Model<L, C, M> {
fn tick_logic(&mut self) {
if self.previous_poll.elapsed() >= DEVICE_POLL_PERIOD {
let devices = block_on(self.apis.ledger_api.discover_devices());
let state_devices = self.devices.clone();
let apis = self.apis.clone();

tokio::task::spawn(async move {
let devices = apis.ledger_api.discover_devices().await;
let mut devices_with_info = Vec::with_capacity(devices.len());

for device in devices {
let info = block_on(self.apis.ledger_api.get_device_info(&device));
let info = apis.ledger_api.get_device_info(&device).await;
if let Some(info) = info {
devices_with_info.push((device, info));
}
}

self.devices = devices_with_info;
*state_devices
.lock()
.expect("Failed to acquire lock on mutex") = devices_with_info;
});

self.previous_poll = Instant::now();
}
let devices = self
.devices
.lock()
.expect("Failed to acquire lock on mutex");

if self.devices.is_empty() {
if devices.is_empty() {
self.selected_device = None;
}

if let Some(selected) = self.selected_device.as_mut() {
if *selected >= self.devices.len() {
*selected = self.devices.len() - 1;
if *selected >= devices.len() {
*selected = devices.len() - 1;
}
}
}
Expand All @@ -66,8 +67,7 @@ impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> ScreenT<L, C,
{
fn construct(state: StateRegistry, api_registry: Arc<ApiRegistry<L, C, M>>) -> Self {
Self {
devices: vec![],
previous_poll: Instant::now() - DEVICE_POLL_PERIOD,
devices: Arc::new(Mutex::new(vec![])),
selected_device: None,
show_navigation_help: false,

Expand Down
40 changes: 24 additions & 16 deletions app/src/screen/device_selection/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,34 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
.title_alignment(Alignment::Center);

let mut list_height = 0;
let list = List::new(model.devices.iter().enumerate().map(|(idx, (_, info))| {
let label = format!(
"{} MCU v{} SE v{}",
info.model, info.mcu_version, info.se_version
);
let list = List::new(
model
.devices
.lock()
.expect("Failed to acquire lock on mutex")
.iter()
.enumerate()
.map(|(idx, (_, info))| {
let label = format!(
"{} MCU v{} SE v{}",
info.model, info.mcu_version, info.se_version
);

let item = Text::centered(label.into());
let item = Text::centered(label.into());

let item = if Some(idx) == model.selected_device {
item.bold()
.bg(resources.accent_color)
.fg(resources.background_color)
} else {
item.fg(resources.main_color)
};
let item = if Some(idx) == model.selected_device {
item.bold()
.bg(resources.accent_color)
.fg(resources.background_color)
} else {
item.fg(resources.main_color)
};

list_height += item.height();
list_height += item.height();

item
}));
item
}),
);

let list_area = list_block.inner(area);
let margin = list_area.height.saturating_sub(list_height as u16) / 2;
Expand Down
39 changes: 26 additions & 13 deletions app/src/screen/portfolio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct Model<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> {
selected_account: Option<(NetworkIdx, AccountIdx)>,
// TODO: Store it in API cache.
coin_prices: Arc<Mutex<HashMap<Network, Option<Decimal>>>>,
balances: HashMap<(Network, Account), Decimal>,
balances: Arc<Mutex<HashMap<(Network, Account), Decimal>>>,
show_navigation_help: bool,

state: StateRegistry,
Expand Down Expand Up @@ -62,9 +62,8 @@ impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> Model<L, C, M
}
}

//let coin_price_api = self.apis.coin_price_api.clone();
let apis = self.apis.clone();
let self_coin_prices = self.coin_prices.clone();
let state_coin_prices = self.coin_prices.clone();

tokio::task::spawn(async move {
let mut coin_prices = HashMap::new();
Expand All @@ -80,23 +79,37 @@ impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> Model<L, C, M
coin_prices.insert(network, price);
}

let mut guard = self_coin_prices.lock().expect("Failed to lock mutex");
let mut guard = state_coin_prices
.lock()
.expect("Failed to acquire lock on mutex");
*guard = coin_prices;
});

// TODO: Don't request balance each tick.
if let Some(accounts) = self.state.device_accounts.as_ref() {
if let Some(accounts) = self.state.device_accounts.clone() {
for (network, accounts) in accounts {
for account in accounts {
self.balances
.entry((*network, account.clone()))
.or_insert_with(|| {
block_on(
self.apis
.blockchain_monitoring_api
.get_balance(*network, account),
)
if !self
.balances
.lock()
.expect("Failed to acquire lock on mutex")
.contains_key(&(network, account.clone()))
{
let apis = self.apis.clone();
let balances = self.balances.clone();

tokio::task::spawn(async move {
let balance = apis
.blockchain_monitoring_api
.get_balance(network, &account)
.await;

balances
.lock()
.expect("Failed to acquire lock on mutex")
.insert((network, account), balance);
});
}
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion app/src/screen/portfolio/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ fn render_account_table<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoring
.map(|account| {
(
account.clone(),
model.balances.get(&(*network, account.clone())).copied(),
model
.balances
.lock()
.expect("Failed to acquire lock on mutex")
.get(&(*network, account.clone()))
.copied(),
)
})
.collect();
Expand Down

0 comments on commit fe8a248

Please sign in to comment.