better peer selection

This commit is contained in:
Joe Ardent 2025-08-03 16:43:25 -07:00
parent 2ff3e3971d
commit 358b96a744
5 changed files with 77 additions and 27 deletions

View file

@ -6,20 +6,29 @@ use futures::{FutureExt, StreamExt};
use joecalsend::{JoecalService, ReceiveDialog, ReceiveRequest, TransferEvent, error::Result};
use julid::Julid;
use log::{LevelFilter, debug, error, warn};
use ratatui::{Frame, widgets::TableState};
use ratatui::{
Frame,
widgets::{ListState, TableState},
};
use ratatui_explorer::FileExplorer;
use tokio::sync::mpsc::UnboundedReceiver;
pub mod widgets;
pub type Peers = BTreeMap<SocketAddr, (String, String)>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Peer {
pub alias: String,
pub fingerprint: String,
pub addr: SocketAddr,
}
pub struct App {
pub service: JoecalService,
pub screen: Vec<CurrentScreen>,
pub events: EventStream,
// addr -> (alias, fingerprint)
pub peers: Peers,
pub peers: Vec<Peer>,
pub peer_state: ListState,
pub receive_requests: BTreeMap<Julid, ReceiveRequest>,
receiving_state: TableState,
// for getting messages back from the web server or web client about things we've done; the
@ -55,6 +64,7 @@ impl App {
content: None,
events: Default::default(),
peers: Default::default(),
peer_state: Default::default(),
receive_requests: Default::default(),
receiving_state: Default::default(),
}
@ -124,6 +134,8 @@ impl App {
KeyCode::Char('q') => self.exit(),
KeyCode::Tab => *s = SendingScreen::Files,
KeyCode::Enter => self.send_content().await,
KeyCode::Up => self.peer_state.select_previous(),
KeyCode::Down => self.peer_state.select_next(),
_ => {}
},
SendingScreen::Text => {}
@ -234,11 +246,16 @@ impl App {
error!("could not list directory {file:?}: {e}");
}
if let Some((_, (_, peer))) = self.peers.first_key_value()
if let Some(idx) = self.peer_state.selected()
&& let Some(peer) = self.peers.get(idx)
&& file.is_file()
{
debug!("sending {file:?}");
if let Err(e) = self.service.send_file(peer.to_owned(), file.clone()).await {
if let Err(e) = self
.service
.send_file(&peer.fingerprint, file.clone())
.await
{
error!("got error sending content: {e:?}");
}
}

View file

@ -8,11 +8,14 @@ use ratatui::{
style::{Color, Style, Stylize},
symbols::border,
text::{Line, Text, ToLine},
widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Row, Table, TableState, Widget},
widgets::{
Block, Borders, List, ListItem, ListState, Padding, Paragraph, Row, Table, TableState,
Widget,
},
};
use tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget, TuiWidgetState};
use super::{App, CurrentScreen, Peers, SendingScreen};
use super::{App, CurrentScreen, Peer, SendingScreen};
static MAIN_MENU: LazyLock<Line> = LazyLock::new(|| {
Line::from(vec![
@ -126,7 +129,12 @@ impl Widget for &mut App {
outer_frame(*current_screen, &MAIN_MENU, area, buf);
logger(header_right.inner(header_margin), buf);
let peers = PeersWidget { peers: &self.peers };
peers.render(footer_right.inner(footer_margin), buf);
ratatui::widgets::StatefulWidget::render(
peers,
footer_right.inner(footer_margin),
buf,
&mut self.peer_state,
);
NetworkInfoWidget.render(footer_left.inner(footer_margin), buf);
receive_requests(
&rx_reqs,
@ -168,7 +176,12 @@ impl Widget for &mut App {
.render(header_left.inner(header_margin), buf);
logger(header_right.inner(header_margin), buf);
peers.render(bottom.inner(subscreen_margin), buf);
ratatui::widgets::StatefulWidget::render(
peers,
bottom.inner(subscreen_margin),
buf,
&mut self.peer_state,
);
}
_ => {
outer_frame(*current_screen, &MAIN_MENU, area, buf);
@ -261,19 +274,32 @@ fn receive_requests(
#[derive(Debug, Clone)]
pub struct PeersWidget<'p> {
pub peers: &'p Peers,
pub peers: &'p [Peer],
}
impl<'p> Widget for PeersWidget<'p> {
fn render(self, area: Rect, buf: &mut Buffer)
impl<'p> ratatui::widgets::StatefulWidget for PeersWidget<'p> {
type State = ListState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
where
Self: Sized,
{
let mut items = Vec::with_capacity(self.peers.len());
for (k, v) in self.peers.iter() {
let item = format!("{:?}: {} ({})", k, v.0, v.1);
let s = Line::from(item.yellow());
if self.peers.is_empty() {
state.select(None);
}
if state.selected().is_none() {
state.select(Some(0));
}
let mut items = Vec::with_capacity(self.peers.len());
for Peer {
addr,
alias,
fingerprint,
} in self.peers.iter()
{
let item = format!("{:?}: {} ({})", addr, alias, fingerprint);
let s = Line::from(item.yellow());
let item = ListItem::new(s);
items.push(item);
}
@ -282,8 +308,10 @@ impl<'p> Widget for PeersWidget<'p> {
.title(title)
.borders(Borders::all())
.padding(Padding::uniform(1));
let list = List::new(items).block(block);
list.render(area, buf);
let list = List::new(items)
.block(block)
.highlight_style(Style::new().bg(Color::Rgb(99, 99, 99)));
ratatui::widgets::StatefulWidget::render(list, area, buf, state);
}
}

View file

@ -86,6 +86,7 @@ impl JoecalService {
socket.join_multicast_v4(MULTICAST_IP, Ipv4Addr::from_bits(0))?;
let client = reqwest::ClientBuilder::new()
// localsend certs are self-signed
.danger_accept_invalid_certs(true)
.build()?;

View file

@ -5,7 +5,7 @@ use tokio::{sync::mpsc::unbounded_channel, task::JoinSet};
use tui_logger::{LevelFilter, init_logger, set_env_filter_from_env};
mod app;
use app::{App, CurrentScreen};
use app::{App, CurrentScreen, Peer};
fn main() -> error::Result<()> {
let device = Device::default();
@ -58,18 +58,22 @@ async fn start_and_run(
app.peers.clear();
peers.iter().for_each(|(fingerprint, (addr, device))| {
let alias = device.alias.clone();
app.peers
.insert(addr.to_owned(), (alias, fingerprint.to_owned()));
let peer = Peer {
alias,
fingerprint: fingerprint.to_owned(),
addr: addr.to_owned(),
};
app.peers.push(peer);
});
let mut stale_uploads = Vec::with_capacity(app.receive_requests.len());
let mut stale_rx_requests = Vec::with_capacity(app.receive_requests.len());
let now = chrono::Utc::now().timestamp_millis() as u64;
for (id, request) in app.receive_requests.iter() {
if request.tx.is_closed() || (now - id.timestamp()) > 60_000 {
stale_uploads.push(*id);
stale_rx_requests.push(*id);
}
}
for id in stale_uploads {
for id in stale_rx_requests {
app.receive_requests.remove(&id);
}
}

View file

@ -55,10 +55,10 @@ pub struct PrepareUploadRequest {
impl JoecalService {
pub async fn prepare_upload(
&self,
peer: String,
peer: &str,
files: BTreeMap<String, FileMetadata>,
) -> Result<PrepareUploadResponse> {
let Some((addr, device)) = self.peers.lock().await.get(&peer).cloned() else {
let Some((addr, device)) = self.peers.lock().await.get(peer).cloned() else {
return Err(LocalSendError::PeerNotFound);
};
@ -135,7 +135,7 @@ impl JoecalService {
Ok(())
}
pub async fn send_file(&self, peer: String, file_path: PathBuf) -> Result<()> {
pub async fn send_file(&self, peer: &str, file_path: PathBuf) -> Result<()> {
// Generate file metadata
let file_metadata = FileMetadata::from_path(&file_path)?;