Compare commits
3 commits
b5a950d49e
...
75677faeae
Author | SHA1 | Date | |
---|---|---|---|
|
75677faeae | ||
|
a2c6f7f8e7 | ||
|
cac0e4f6e3 |
6 changed files with 808 additions and 146 deletions
787
Cargo.lock
generated
787
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -16,7 +16,7 @@ mime = "0.3"
|
||||||
mime_guess = "2"
|
mime_guess = "2"
|
||||||
native-dialog = "0.9"
|
native-dialog = "0.9"
|
||||||
network-interface = { version = "2", features = ["serde"] }
|
network-interface = { version = "2", features = ["serde"] }
|
||||||
ratatui = "0.29"
|
ratatui = "0.30.0-alpha.5"
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
use std::{
|
use std::{collections::BTreeMap, io, net::SocketAddr, sync::OnceLock, time::Duration};
|
||||||
collections::{BTreeMap, VecDeque},
|
|
||||||
io,
|
|
||||||
net::SocketAddr,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind};
|
use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind};
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use joecalsend::JoecalState;
|
use joecalsend::{Config, JoecalState, Listeners, models::Device};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal,
|
DefaultTerminal,
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
|
@ -17,13 +12,14 @@ use ratatui::{
|
||||||
text::{Line, Text},
|
text::{Line, Text},
|
||||||
widgets::{Block, Paragraph, Widget},
|
widgets::{Block, Paragraph, Widget},
|
||||||
};
|
};
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
pub type Peers = BTreeMap<SocketAddr, (String, String)>;
|
pub type Peers = BTreeMap<SocketAddr, (String, String)>;
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub state: JoecalState,
|
pub state: OnceLock<JoecalState>,
|
||||||
pub screen: Vec<CurrentScreen>,
|
pub screen: Vec<CurrentScreen>,
|
||||||
pub events: EventStream,
|
pub events: EventStream,
|
||||||
// addr -> (alias, fingerprint)
|
// addr -> (alias, fingerprint)
|
||||||
|
@ -39,16 +35,29 @@ pub enum CurrentScreen {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(state: JoecalState) -> Self {
|
pub fn new() -> Self {
|
||||||
App {
|
App {
|
||||||
state,
|
state: Default::default(),
|
||||||
screen: vec![CurrentScreen::Main],
|
screen: vec![CurrentScreen::Main],
|
||||||
peers: Default::default(),
|
peers: Default::default(),
|
||||||
events: Default::default(),
|
events: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
#[tokio::main]
|
||||||
|
pub async fn run(
|
||||||
|
&mut self,
|
||||||
|
terminal: &mut DefaultTerminal,
|
||||||
|
config: Config,
|
||||||
|
device: Device,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let state = JoecalState::new(device)
|
||||||
|
.await
|
||||||
|
.expect("Could not create JoecalState");
|
||||||
|
|
||||||
|
let mut handles = JoinSet::new();
|
||||||
|
state.start(&config, &mut handles).await;
|
||||||
|
self.state.get_or_init(|| state);
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|frame| self.draw(frame))?;
|
terminal.draw(|frame| self.draw(frame))?;
|
||||||
self.handle_events().await?;
|
self.handle_events().await?;
|
||||||
|
@ -56,25 +65,25 @@ impl App {
|
||||||
if let Some(&top) = self.screen.last()
|
if let Some(&top) = self.screen.last()
|
||||||
&& top == CurrentScreen::Stopping
|
&& top == CurrentScreen::Stopping
|
||||||
{
|
{
|
||||||
self.state.stop().await;
|
self.state.get().unwrap().stop().await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let peers = self.state.peers.lock().await;
|
let peers = self.state.get().unwrap().peers.lock().await;
|
||||||
self.peers.clear();
|
self.peers.clear();
|
||||||
peers.iter().for_each(|(k, v)| {
|
peers.iter().for_each(|(fingerprint, (addr, device))| {
|
||||||
// k is fingerprint, v is addr, device
|
let alias = device.alias.clone();
|
||||||
let addr = v.0;
|
self.peers
|
||||||
let alias = v.1.alias.clone();
|
.insert(addr.to_owned(), (alias, fingerprint.to_owned()));
|
||||||
let fingerprint = k.clone();
|
|
||||||
self.peers.insert(addr, (alias, fingerprint));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shutdown(&mut handles).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_events(&mut self) -> io::Result<()> {
|
async fn handle_events(&mut self) -> io::Result<()> {
|
||||||
let mut tick = tokio::time::interval(Duration::from_millis(100));
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
event = self.events.next().fuse() => {
|
event = self.events.next().fuse() => {
|
||||||
if let Some(Ok(evt)) = event {
|
if let Some(Ok(evt)) = event {
|
||||||
|
@ -88,7 +97,7 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = tick.tick() => {}
|
_ = tokio::time::sleep(Duration::from_millis(200)) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -109,18 +118,48 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send(&mut self) {
|
fn send(&mut self) {
|
||||||
self.screen.push(CurrentScreen::Sending);
|
let last = self.screen.last();
|
||||||
|
match last {
|
||||||
|
Some(CurrentScreen::Sending) => {}
|
||||||
|
_ => self.screen.push(CurrentScreen::Sending),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recv(&mut self) {
|
fn recv(&mut self) {
|
||||||
self.screen.push(CurrentScreen::Receiving);
|
let last = self.screen.last();
|
||||||
|
match last {
|
||||||
|
Some(CurrentScreen::Receiving) => {}
|
||||||
|
_ => self.screen.push(CurrentScreen::Receiving),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop(&mut self) {
|
fn pop(&mut self) {
|
||||||
|
self.screen.pop();
|
||||||
if self.screen.last().is_none() {
|
if self.screen.last().is_none() {
|
||||||
self.screen.push(CurrentScreen::Main);
|
self.screen.push(CurrentScreen::Main);
|
||||||
} else {
|
}
|
||||||
self.screen.pop();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn shutdown(handles: &mut JoinSet<Listeners>) {
|
||||||
|
let mut alarm = tokio::time::interval(tokio::time::Duration::from_secs(5));
|
||||||
|
alarm.tick().await;
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
join_result = handles.join_next() => {
|
||||||
|
match join_result {
|
||||||
|
Some(handle) => match handle {
|
||||||
|
Ok(h) => println!("Stopped {h:?}"),
|
||||||
|
Err(e) => println!("Got error {e:?}"),
|
||||||
|
}
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = alarm.tick() => {
|
||||||
|
println!("Exit timeout reached, aborting all unjoined tasks");
|
||||||
|
handles.abort_all();
|
||||||
|
break;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Margin, Rect},
|
||||||
style::Stylize,
|
style::Stylize,
|
||||||
text::Line,
|
text::Line,
|
||||||
widgets::{Block, Borders, List, ListItem, Padding},
|
widgets::{Block, Borders, List, ListItem, Padding},
|
||||||
|
@ -34,8 +34,8 @@ impl App {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
network_info(frame, footer_left);
|
network_info(frame, footer_left.inner(Margin::new(1, 1)));
|
||||||
peers(&self.peers, frame, footer_right);
|
peers(&self.peers, frame, footer_right.inner(Margin::new(1, 1)));
|
||||||
// draw the main frame last
|
// draw the main frame last
|
||||||
frame.render_widget(self, frame.area());
|
frame.render_widget(self, frame.area());
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ fn network_info(frame: &mut Frame, area: Rect) {
|
||||||
)
|
)
|
||||||
.yellow()
|
.yellow()
|
||||||
.into();
|
.into();
|
||||||
let http: Line = format!(" HTTP address:\t\t{:?}", joecalsend::LISTENING_SOCKET_ADDR)
|
let http: Line = format!(" HTTP address:\t{:?}", joecalsend::LISTENING_SOCKET_ADDR)
|
||||||
.yellow()
|
.yellow()
|
||||||
.into();
|
.into();
|
||||||
let items = [
|
let items = [
|
||||||
|
|
25
src/lib.rs
25
src/lib.rs
|
@ -8,7 +8,6 @@ use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||||
sync::{Arc, OnceLock},
|
sync::{Arc, OnceLock},
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use models::Device;
|
use models::Device;
|
||||||
|
@ -108,22 +107,14 @@ impl JoecalState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn stop(&self) {
|
pub async fn stop(&self) {
|
||||||
loop {
|
let mut rstate = self.running_state.lock().await;
|
||||||
let mut rstate = self.running_state.lock().await;
|
*rstate = RunningState::Stopping;
|
||||||
*rstate = RunningState::Stopping;
|
let _ = self
|
||||||
if self
|
.stop_tx
|
||||||
.stop_tx
|
.get()
|
||||||
.get()
|
.expect("Could not get stop signal transmitter")
|
||||||
.expect("Could not get stop signal transmitter")
|
.send(())
|
||||||
.send(())
|
.await;
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
tokio::time::sleep(Duration::from_millis(777)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn refresh_peers(&self) {
|
pub async fn refresh_peers(&self) {
|
||||||
|
|
43
src/main.rs
43
src/main.rs
|
@ -1,15 +1,13 @@
|
||||||
#![feature(slice_as_array)]
|
#![feature(slice_as_array)]
|
||||||
|
|
||||||
use frontend::App;
|
use frontend::App;
|
||||||
use joecalsend::{Config, JoecalState, error, models::Device};
|
use joecalsend::{Config, error, models::Device};
|
||||||
use local_ip_address::local_ip;
|
use local_ip_address::local_ip;
|
||||||
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig, V4IfAddr};
|
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig, V4IfAddr};
|
||||||
use tokio::task::JoinSet;
|
|
||||||
|
|
||||||
mod frontend;
|
mod frontend;
|
||||||
|
|
||||||
#[tokio::main]
|
fn main() -> error::Result<()> {
|
||||||
async fn main() -> error::Result<()> {
|
|
||||||
let device = Device::default();
|
let device = Device::default();
|
||||||
dbg!(&device);
|
dbg!(&device);
|
||||||
|
|
||||||
|
@ -18,7 +16,7 @@ async fn main() -> error::Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// for enumerating subnet peers when multicast fails (https://github.com/localsend/protocol?tab=readme-ov-file#32-http-legacy-mode)
|
// for enumerating subnet peers when multicast fails (https://github.com/localsend/protocol?tab=readme-ov-file#32-http-legacy-mode)
|
||||||
let mut network_ip = ip;
|
let mut _network_ip = ip;
|
||||||
let nifs = NetworkInterface::show().unwrap();
|
let nifs = NetworkInterface::show().unwrap();
|
||||||
for addr in nifs.into_iter().flat_map(|i| i.addr) {
|
for addr in nifs.into_iter().flat_map(|i| i.addr) {
|
||||||
if let Addr::V4(V4IfAddr {
|
if let Addr::V4(V4IfAddr {
|
||||||
|
@ -28,43 +26,14 @@ async fn main() -> error::Result<()> {
|
||||||
}) = addr
|
}) = addr
|
||||||
&& ip == ifip
|
&& ip == ifip
|
||||||
{
|
{
|
||||||
network_ip = ip & netmask;
|
_network_ip = ip & netmask;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = JoecalState::new(device)
|
|
||||||
.await
|
|
||||||
.expect("Could not create application session");
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let mut handles = JoinSet::new();
|
|
||||||
state.start(&config, &mut handles).await;
|
|
||||||
let mut app = App::new(state.clone());
|
|
||||||
let mut terminal = ratatui::init();
|
|
||||||
let result = app.run(&mut terminal).await;
|
|
||||||
ratatui::restore();
|
|
||||||
|
|
||||||
let mut alarm = tokio::time::interval(tokio::time::Duration::from_secs(5));
|
let mut app = App::new();
|
||||||
alarm.tick().await;
|
|
||||||
|
|
||||||
loop {
|
Ok(ratatui::run(|terminal| app.run(terminal, config, device))?)
|
||||||
tokio::select! {
|
|
||||||
handle = handles.join_next() => {
|
|
||||||
match handle {
|
|
||||||
Some(handle) => match handle {
|
|
||||||
Ok(h) => println!("Stopped {h:?}"),
|
|
||||||
Err(e) => println!("Got error {e:?}"),
|
|
||||||
}
|
|
||||||
None => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = alarm.tick() => {
|
|
||||||
println!("Exit timeout reached, aborting all unjoined tasks");
|
|
||||||
handles.abort_all();
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result?)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue