joecalsend/src/main.rs
2025-08-14 16:25:32 -07:00

138 lines
4.2 KiB
Rust

use std::{path::Path, str::FromStr, time::Duration};
use clap::Parser;
use jocalsend::{Config, DEFAULT_INTERVAL, JocalService, JocalTasks, error::Result};
use log::{error, info};
use ratatui::DefaultTerminal;
use ratatui_explorer::FileExplorer;
use tokio::task::JoinSet;
use tui_logger::{LevelFilter, init_logger, set_env_filter_from_env};
mod app;
use app::{App, CurrentScreen, Peer};
mod cli;
use cli::Cli;
fn main() -> Result<()> {
// just in case we need to display the help
let _ = Cli::parse();
if std::env::var("RUST_LOG").is_err() {
unsafe {
std::env::set_var("RUST_LOG", "jocalsend");
}
}
init_logger(LevelFilter::Info).map_err(|e| std::io::Error::other(format!("{e}")))?;
set_env_filter_from_env(None);
let config = Config::new()?;
let mut terminal = ratatui::init();
let result = start_and_run(&mut terminal, config);
ratatui::restore();
result
}
#[tokio::main(flavor = "multi_thread")]
async fn start_and_run(terminal: &mut DefaultTerminal, config: Config) -> Result<()> {
let (service, event_listener) = JocalService::new(config.clone()).await?;
let mut app = App::new(service, event_listener);
let cli = Cli::parse();
if let Some(text) = cli.text {
let i = app.input();
*i = i.clone().with_value(text.clone());
app.text().replace(text);
}
if let Some(file) = cli.file {
let path = std::path::PathBuf::from_str(&file.to_string_lossy())
.unwrap_or_else(|_| panic!("Could not create path from {file:?}"));
set_file_selection(&path, app.files());
}
let mut handles = JoinSet::new();
app.service.start(&mut handles).await;
let shutdown = shutdown(&mut handles);
let mut shutdown = std::pin::pin!(shutdown);
loop {
terminal.draw(|frame| app.draw(frame))?;
if app.screen() == CurrentScreen::Stopping {
tokio::select! {
_ = shutdown.as_mut() => {
break;
}
_ = tokio::time::sleep(DEFAULT_INTERVAL) => {}
}
} else {
app.handle_events().await?;
let peers = app.service.peers.lock().await;
app.peers.clear();
peers.iter().for_each(|(fingerprint, (addr, device))| {
let alias = device.alias.clone();
let peer = Peer {
alias,
fingerprint: fingerprint.to_owned(),
addr: addr.to_owned(),
};
app.peers.push(peer);
});
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_rx_requests.push(*id);
}
}
for id in stale_rx_requests {
app.receive_requests.remove(&id);
}
}
}
Ok(())
}
async fn shutdown(handles: &mut JoinSet<JocalTasks>) {
let mut timeout = tokio::time::interval(Duration::from_secs(5));
timeout.tick().await;
loop {
tokio::select! {
join_result = handles.join_next() => {
match join_result {
Some(handle) => match handle {
Ok(h) => info!("Stopped {h:?}"),
Err(e) => error!("Got error {e:?}"),
}
None => break,
}
}
_ = timeout.tick() => {
info!("Exit timeout reached, aborting all unjoined tasks");
handles.abort_all();
break;
},
}
}
}
fn set_file_selection(path: &Path, explorer: &mut FileExplorer) {
let parent = path.parent().map(|f| f.to_path_buf()).unwrap_or("/".into());
let _ = explorer.set_cwd(parent);
let files = explorer.files();
let mut idx = None;
for (i, f) in files.iter().enumerate() {
if f.name() == path.file_name().unwrap().to_string_lossy() {
idx = Some(i);
break;
}
}
if let Some(idx) = idx {
explorer.set_selected_idx(idx);
}
}