better peer selection
This commit is contained in:
parent
2ff3e3971d
commit
358b96a744
5 changed files with 77 additions and 27 deletions
|
@ -6,20 +6,29 @@ use futures::{FutureExt, StreamExt};
|
||||||
use joecalsend::{JoecalService, ReceiveDialog, ReceiveRequest, TransferEvent, error::Result};
|
use joecalsend::{JoecalService, ReceiveDialog, ReceiveRequest, TransferEvent, error::Result};
|
||||||
use julid::Julid;
|
use julid::Julid;
|
||||||
use log::{LevelFilter, debug, error, warn};
|
use log::{LevelFilter, debug, error, warn};
|
||||||
use ratatui::{Frame, widgets::TableState};
|
use ratatui::{
|
||||||
|
Frame,
|
||||||
|
widgets::{ListState, TableState},
|
||||||
|
};
|
||||||
use ratatui_explorer::FileExplorer;
|
use ratatui_explorer::FileExplorer;
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
|
|
||||||
pub mod widgets;
|
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 struct App {
|
||||||
pub service: JoecalService,
|
pub service: JoecalService,
|
||||||
pub screen: Vec<CurrentScreen>,
|
pub screen: Vec<CurrentScreen>,
|
||||||
pub events: EventStream,
|
pub events: EventStream,
|
||||||
// addr -> (alias, fingerprint)
|
// addr -> (alias, fingerprint)
|
||||||
pub peers: Peers,
|
pub peers: Vec<Peer>,
|
||||||
|
pub peer_state: ListState,
|
||||||
pub receive_requests: BTreeMap<Julid, ReceiveRequest>,
|
pub receive_requests: BTreeMap<Julid, ReceiveRequest>,
|
||||||
receiving_state: TableState,
|
receiving_state: TableState,
|
||||||
// for getting messages back from the web server or web client about things we've done; the
|
// 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,
|
content: None,
|
||||||
events: Default::default(),
|
events: Default::default(),
|
||||||
peers: Default::default(),
|
peers: Default::default(),
|
||||||
|
peer_state: Default::default(),
|
||||||
receive_requests: Default::default(),
|
receive_requests: Default::default(),
|
||||||
receiving_state: Default::default(),
|
receiving_state: Default::default(),
|
||||||
}
|
}
|
||||||
|
@ -124,6 +134,8 @@ impl App {
|
||||||
KeyCode::Char('q') => self.exit(),
|
KeyCode::Char('q') => self.exit(),
|
||||||
KeyCode::Tab => *s = SendingScreen::Files,
|
KeyCode::Tab => *s = SendingScreen::Files,
|
||||||
KeyCode::Enter => self.send_content().await,
|
KeyCode::Enter => self.send_content().await,
|
||||||
|
KeyCode::Up => self.peer_state.select_previous(),
|
||||||
|
KeyCode::Down => self.peer_state.select_next(),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
SendingScreen::Text => {}
|
SendingScreen::Text => {}
|
||||||
|
@ -234,11 +246,16 @@ impl App {
|
||||||
error!("could not list directory {file:?}: {e}");
|
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()
|
&& file.is_file()
|
||||||
{
|
{
|
||||||
debug!("sending {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:?}");
|
error!("got error sending content: {e:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,14 @@ use ratatui::{
|
||||||
style::{Color, Style, Stylize},
|
style::{Color, Style, Stylize},
|
||||||
symbols::border,
|
symbols::border,
|
||||||
text::{Line, Text, ToLine},
|
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 tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget, TuiWidgetState};
|
||||||
|
|
||||||
use super::{App, CurrentScreen, Peers, SendingScreen};
|
use super::{App, CurrentScreen, Peer, SendingScreen};
|
||||||
|
|
||||||
static MAIN_MENU: LazyLock<Line> = LazyLock::new(|| {
|
static MAIN_MENU: LazyLock<Line> = LazyLock::new(|| {
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
|
@ -126,7 +129,12 @@ impl Widget for &mut App {
|
||||||
outer_frame(*current_screen, &MAIN_MENU, area, buf);
|
outer_frame(*current_screen, &MAIN_MENU, area, buf);
|
||||||
logger(header_right.inner(header_margin), buf);
|
logger(header_right.inner(header_margin), buf);
|
||||||
let peers = PeersWidget { peers: &self.peers };
|
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);
|
NetworkInfoWidget.render(footer_left.inner(footer_margin), buf);
|
||||||
receive_requests(
|
receive_requests(
|
||||||
&rx_reqs,
|
&rx_reqs,
|
||||||
|
@ -168,7 +176,12 @@ impl Widget for &mut App {
|
||||||
.render(header_left.inner(header_margin), buf);
|
.render(header_left.inner(header_margin), buf);
|
||||||
logger(header_right.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);
|
outer_frame(*current_screen, &MAIN_MENU, area, buf);
|
||||||
|
@ -261,19 +274,32 @@ fn receive_requests(
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PeersWidget<'p> {
|
pub struct PeersWidget<'p> {
|
||||||
pub peers: &'p Peers,
|
pub peers: &'p [Peer],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'p> Widget for PeersWidget<'p> {
|
impl<'p> ratatui::widgets::StatefulWidget for PeersWidget<'p> {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer)
|
type State = ListState;
|
||||||
|
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let mut items = Vec::with_capacity(self.peers.len());
|
if self.peers.is_empty() {
|
||||||
for (k, v) in self.peers.iter() {
|
state.select(None);
|
||||||
let item = format!("{:?}: {} ({})", k, v.0, v.1);
|
}
|
||||||
let s = Line::from(item.yellow());
|
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);
|
let item = ListItem::new(s);
|
||||||
items.push(item);
|
items.push(item);
|
||||||
}
|
}
|
||||||
|
@ -282,8 +308,10 @@ impl<'p> Widget for PeersWidget<'p> {
|
||||||
.title(title)
|
.title(title)
|
||||||
.borders(Borders::all())
|
.borders(Borders::all())
|
||||||
.padding(Padding::uniform(1));
|
.padding(Padding::uniform(1));
|
||||||
let list = List::new(items).block(block);
|
let list = List::new(items)
|
||||||
list.render(area, buf);
|
.block(block)
|
||||||
|
.highlight_style(Style::new().bg(Color::Rgb(99, 99, 99)));
|
||||||
|
ratatui::widgets::StatefulWidget::render(list, area, buf, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,7 @@ impl JoecalService {
|
||||||
socket.join_multicast_v4(MULTICAST_IP, Ipv4Addr::from_bits(0))?;
|
socket.join_multicast_v4(MULTICAST_IP, Ipv4Addr::from_bits(0))?;
|
||||||
|
|
||||||
let client = reqwest::ClientBuilder::new()
|
let client = reqwest::ClientBuilder::new()
|
||||||
|
// localsend certs are self-signed
|
||||||
.danger_accept_invalid_certs(true)
|
.danger_accept_invalid_certs(true)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -5,7 +5,7 @@ use tokio::{sync::mpsc::unbounded_channel, task::JoinSet};
|
||||||
use tui_logger::{LevelFilter, init_logger, set_env_filter_from_env};
|
use tui_logger::{LevelFilter, init_logger, set_env_filter_from_env};
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
use app::{App, CurrentScreen};
|
use app::{App, CurrentScreen, Peer};
|
||||||
|
|
||||||
fn main() -> error::Result<()> {
|
fn main() -> error::Result<()> {
|
||||||
let device = Device::default();
|
let device = Device::default();
|
||||||
|
@ -58,18 +58,22 @@ async fn start_and_run(
|
||||||
app.peers.clear();
|
app.peers.clear();
|
||||||
peers.iter().for_each(|(fingerprint, (addr, device))| {
|
peers.iter().for_each(|(fingerprint, (addr, device))| {
|
||||||
let alias = device.alias.clone();
|
let alias = device.alias.clone();
|
||||||
app.peers
|
let peer = Peer {
|
||||||
.insert(addr.to_owned(), (alias, fingerprint.to_owned()));
|
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;
|
let now = chrono::Utc::now().timestamp_millis() as u64;
|
||||||
for (id, request) in app.receive_requests.iter() {
|
for (id, request) in app.receive_requests.iter() {
|
||||||
if request.tx.is_closed() || (now - id.timestamp()) > 60_000 {
|
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);
|
app.receive_requests.remove(&id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,10 +55,10 @@ pub struct PrepareUploadRequest {
|
||||||
impl JoecalService {
|
impl JoecalService {
|
||||||
pub async fn prepare_upload(
|
pub async fn prepare_upload(
|
||||||
&self,
|
&self,
|
||||||
peer: String,
|
peer: &str,
|
||||||
files: BTreeMap<String, FileMetadata>,
|
files: BTreeMap<String, FileMetadata>,
|
||||||
) -> Result<PrepareUploadResponse> {
|
) -> 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);
|
return Err(LocalSendError::PeerNotFound);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ impl JoecalService {
|
||||||
Ok(())
|
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
|
// Generate file metadata
|
||||||
let file_metadata = FileMetadata::from_path(&file_path)?;
|
let file_metadata = FileMetadata::from_path(&file_path)?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue