Compare commits

...

2 commits

Author SHA1 Message Date
Joe Ardent
29034e09d1 rename to jocalsend 2025-08-06 14:09:37 -07:00
Joe Ardent
39e21b83b1 better logging, add 'main()' method to app and binding to get to main screen 2025-08-06 14:07:10 -07:00
10 changed files with 44 additions and 37 deletions

2
Cargo.lock generated
View file

@ -1150,7 +1150,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "joecalsend" name = "jocalsend"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",

View file

@ -1,5 +1,5 @@
[package] [package]
name = "joecalsend" name = "jocalsend"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"

View file

@ -2,7 +2,7 @@ use std::{collections::BTreeMap, 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::{JoecalService, ReceiveDialog, ReceiveRequest, TransferEvent, error::Result}; use jocalsend::{JocalService, 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::{ use ratatui::{
@ -23,7 +23,7 @@ pub struct Peer {
} }
pub struct App { pub struct App {
pub service: JoecalService, pub service: JocalService,
pub events: EventStream, pub events: EventStream,
// addr -> (alias, fingerprint) // addr -> (alias, fingerprint)
pub peers: Vec<Peer>, pub peers: Vec<Peer>,
@ -56,7 +56,7 @@ pub enum SendingScreen {
} }
impl App { impl App {
pub fn new(service: JoecalService, event_listener: UnboundedReceiver<TransferEvent>) -> Self { pub fn new(service: JocalService, event_listener: UnboundedReceiver<TransferEvent>) -> Self {
App { App {
service, service,
event_listener, event_listener,
@ -126,6 +126,7 @@ impl App {
KeyCode::Char('s') => self.send(), KeyCode::Char('s') => self.send(),
KeyCode::Char('r') => self.recv(), KeyCode::Char('r') => self.recv(),
KeyCode::Char('l') => self.logs(), KeyCode::Char('l') => self.logs(),
KeyCode::Char('m') => self.main(),
_ => match mode { _ => match mode {
CurrentScreen::Logging => match code { CurrentScreen::Logging => match code {
KeyCode::Left => change_log_level(-1), KeyCode::Left => change_log_level(-1),
@ -230,6 +231,14 @@ impl App {
} }
} }
pub fn main(&mut self) {
let last = self.screen.last();
match last {
Some(CurrentScreen::Main) => {}
_ => self.screen.push(CurrentScreen::Main),
}
}
// accept a content receive request // accept a content receive request
fn accept(&mut self) { fn accept(&mut self) {
let Some(idx) = self.receiving_state.selected() else { let Some(idx) = self.receiving_state.selected() else {

View file

@ -1,6 +1,6 @@
use std::sync::LazyLock; use std::sync::LazyLock;
use joecalsend::ReceiveRequest; use jocalsend::ReceiveRequest;
use log::LevelFilter; use log::LevelFilter;
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
@ -217,7 +217,7 @@ impl Widget for &mut App {
} }
fn outer_frame(screen: &CurrentScreen, menu: &Line, area: Rect, buf: &mut Buffer) { fn outer_frame(screen: &CurrentScreen, menu: &Line, area: Rect, buf: &mut Buffer) {
let title = Line::from(" Joecalsend ".bold()); let title = Line::from(" Jocalsend ".bold());
let block = Block::bordered() let block = Block::bordered()
.title(title.centered()) .title(title.centered())
.title_bottom(menu.clone().centered()) .title_bottom(menu.clone().centered())
@ -240,14 +240,14 @@ fn text_popup(text: &str, title: &str, area: Rect, buf: &mut Buffer) {
block.render(area, buf); block.render(area, buf);
let (_, len) = unicode_segmentation::UnicodeSegmentation::graphemes(text, true).size_hint(); let (_, len) = unicode_segmentation::UnicodeSegmentation::graphemes(text, true).size_hint();
let len = len.unwrap_or(text.len()) as u16; let len = len.unwrap_or(text.len()) as u16 + 2;
let area = centered_rect(area, Constraint::Length(len), Constraint::Length(1)); let area = centered_rect(area, Constraint::Length(len), Constraint::Length(1));
Paragraph::new(text).centered().yellow().render(area, buf); Paragraph::new(text).centered().yellow().render(area, buf);
} }
fn logger(area: Rect, buf: &mut Buffer) { fn logger(area: Rect, buf: &mut Buffer) {
let title = Line::from(log::max_level().as_str()); let title = Line::from(format!(" {} logs ", log::max_level().as_str()));
let logger = TuiLoggerWidget::default() let logger = TuiLoggerWidget::default()
.output_separator('|') .output_separator('|')
.output_timestamp(Some("%H:%M:%S%.3f".to_string())) .output_timestamp(Some("%H:%M:%S%.3f".to_string()))
@ -257,7 +257,7 @@ fn logger(area: Rect, buf: &mut Buffer) {
.output_line(false) .output_line(false)
.block(Block::bordered().title(title.centered())) .block(Block::bordered().title(title.centered()))
.style(Style::default()) .style(Style::default())
.state(&TuiWidgetState::new().set_default_display_level(LevelFilter::Debug)); .state(&TuiWidgetState::new().set_default_display_level(LevelFilter::Trace));
logger.render(area, buf); logger.render(area, buf);
} }
@ -267,7 +267,7 @@ fn receive_requests(
area: Rect, area: Rect,
buf: &mut Buffer, buf: &mut Buffer,
) { ) {
let title = Line::from(" Upload Requests ").bold(); let title = Line::from(" Incoming Transfer Requests ").bold();
let block = Block::bordered().title(title.centered()); let block = Block::bordered().title(title.centered());
let mut rows = Vec::new(); let mut rows = Vec::new();
@ -311,9 +311,7 @@ fn receive_requests(
ratatui::widgets::StatefulWidget::render(table, area, buf, state); ratatui::widgets::StatefulWidget::render(table, area, buf, state);
if let Some(idx) = state.selected() if let Some(idx) = state.selected() {
//&& let Some(area) = preview_area
{
let area = centered_rect(area, Constraint::Percentage(80), Constraint::Max(7)); let area = centered_rect(area, Constraint::Percentage(80), Constraint::Max(7));
let request = requests[idx]; let request = requests[idx];
if let Some(md) = request.files.values().next() if let Some(md) = request.files.values().next()
@ -366,13 +364,13 @@ impl Widget for NetworkInfoWidget {
{ {
let udp = "UDP socket"; let udp = "UDP socket";
let udp = udp.to_line().left_aligned(); let udp = udp.to_line().left_aligned();
let uaddr = format!("{:?}", joecalsend::LISTENING_SOCKET_ADDR); let uaddr = format!("{:?}", jocalsend::LISTENING_SOCKET_ADDR);
let udp = Row::new(vec![udp, uaddr.to_line().right_aligned()]).yellow(); let udp = Row::new(vec![udp, uaddr.to_line().right_aligned()]).yellow();
let mip = format!( let mip = format!(
"{:?}:{:?}", "{:?}:{:?}",
joecalsend::MULTICAST_IP, jocalsend::MULTICAST_IP,
joecalsend::DEFAULT_PORT jocalsend::DEFAULT_PORT
); );
let multicast = "Multicast address"; let multicast = "Multicast address";
let multicast = Row::new(vec![ let multicast = Row::new(vec![
@ -381,7 +379,7 @@ impl Widget for NetworkInfoWidget {
]) ])
.yellow(); .yellow();
let haddr = format!("{:?}", joecalsend::LISTENING_SOCKET_ADDR); let haddr = format!("{:?}", jocalsend::LISTENING_SOCKET_ADDR);
let http = "HTTP address"; let http = "HTTP address";
let http = Row::new(vec![ let http = Row::new(vec![
http.to_line().left_aligned(), http.to_line().left_aligned(),

View file

@ -11,9 +11,9 @@ use axum::{
use log::{debug, error, trace, warn}; use log::{debug, error, trace, warn};
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use crate::{Config, JoecalService, RunningState, models::Device}; use crate::{Config, JocalService, RunningState, models::Device};
impl JoecalService { impl JocalService {
pub async fn announce(&self, socket: Option<SocketAddr>) -> crate::error::Result<()> { pub async fn announce(&self, socket: Option<SocketAddr>) -> crate::error::Result<()> {
trace!("announcing"); trace!("announcing");
announce_http(&self.device, socket, self.client.clone()).await?; announce_http(&self.device, socket, self.client.clone()).await?;
@ -97,7 +97,7 @@ impl JoecalService {
/// Axum request handler for receiving other devices' registration requests. /// Axum request handler for receiving other devices' registration requests.
pub async fn register_device( pub async fn register_device(
State(service): State<JoecalService>, State(service): State<JocalService>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, ConnectInfo(addr): ConnectInfo<SocketAddr>,
Json(device): Json<Device>, Json(device): Json<Device>,
) -> Json<Device> { ) -> Json<Device> {

View file

@ -9,12 +9,12 @@ use tokio::{net::TcpListener, sync::mpsc};
use tower_http::limit::RequestBodyLimitLayer; use tower_http::limit::RequestBodyLimitLayer;
use crate::{ use crate::{
JoecalService, JocalService,
discovery::register_device, discovery::register_device,
transfer::{prepare_upload, receive_upload}, transfer::{prepare_upload, receive_upload},
}; };
impl JoecalService { impl JocalService {
pub async fn start_http_server(&self, stop_rx: mpsc::Receiver<()>) -> crate::error::Result<()> { pub async fn start_http_server(&self, stop_rx: mpsc::Receiver<()>) -> crate::error::Result<()> {
let app = self.create_router(); let app = self.create_router();
// TODO: make addr config // TODO: make addr config

View file

@ -60,7 +60,7 @@ pub struct ReceiveRequest {
/// Contains the main network and backend state for an application session. /// Contains the main network and backend state for an application session.
#[derive(Clone)] #[derive(Clone)]
pub struct JoecalService { pub struct JocalService {
pub device: Device, pub device: Device,
pub peers: Arc<Mutex<BTreeMap<String, (SocketAddr, Device)>>>, pub peers: Arc<Mutex<BTreeMap<String, (SocketAddr, Device)>>>,
pub sessions: Arc<Mutex<BTreeMap<String, Session>>>, // Session ID to Session pub sessions: Arc<Mutex<BTreeMap<String, Session>>>, // Session ID to Session
@ -74,7 +74,7 @@ pub struct JoecalService {
transfer_event_tx: UnboundedSender<TransferEvent>, transfer_event_tx: UnboundedSender<TransferEvent>,
} }
impl JoecalService { impl JocalService {
pub async fn new( pub async fn new(
device: Device, device: Device,
config: Config, config: Config,
@ -190,7 +190,7 @@ pub struct Config {
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
let home = std::env::home_dir().unwrap_or("/tmp".into()); let home = std::env::home_dir().unwrap_or("/tmp".into());
let dd = home.join("joecalsend-downloads"); let dd = home.join("jocalsend-downloads");
Self { Self {
multicast_addr: SocketAddrV4::new(MULTICAST_IP, DEFAULT_PORT), multicast_addr: SocketAddrV4::new(MULTICAST_IP, DEFAULT_PORT),
port: DEFAULT_PORT, port: DEFAULT_PORT,

View file

@ -1,4 +1,4 @@
use joecalsend::{Config, JoecalService, Listeners, error, models::Device}; use jocalsend::{Config, JocalService, Listeners, error, models::Device};
use log::{error, info}; use log::{error, info};
use ratatui::DefaultTerminal; use ratatui::DefaultTerminal;
use tokio::{sync::mpsc::unbounded_channel, task::JoinSet}; use tokio::{sync::mpsc::unbounded_channel, task::JoinSet};
@ -12,10 +12,10 @@ fn main() -> error::Result<()> {
if std::env::var("RUST_LOG").is_err() { if std::env::var("RUST_LOG").is_err() {
unsafe { unsafe {
std::env::set_var("RUST_LOG", "joecalsend"); std::env::set_var("RUST_LOG", "jocalsend");
} }
} }
init_logger(LevelFilter::Debug).map_err(|e| std::io::Error::other(format!("{e}")))?; init_logger(LevelFilter::Info).map_err(|e| std::io::Error::other(format!("{e}")))?;
set_env_filter_from_env(None); set_env_filter_from_env(None);
let config = Config::default(); let config = Config::default();
@ -35,9 +35,9 @@ async fn start_and_run(
) -> error::Result<()> { ) -> error::Result<()> {
let (event_tx, event_listener) = unbounded_channel(); let (event_tx, event_listener) = unbounded_channel();
let service = JoecalService::new(device, config.clone(), event_tx) let service = JocalService::new(device, config.clone(), event_tx)
.await .await
.expect("Could not create JoecalService"); .expect("Could not create JocalService");
let mut app = App::new(service, event_listener); let mut app = App::new(service, event_listener);

View file

@ -120,7 +120,7 @@ pub enum Protocol {
impl Default for Device { impl Default for Device {
fn default() -> Self { fn default() -> Self {
Self { Self {
alias: "Joecalsend".to_string(), alias: "Jocalsend".to_string(),
version: "2.1".to_string(), version: "2.1".to_string(),
device_model: None, device_model: None,
device_type: Some(DeviceType::Headless), device_type: Some(DeviceType::Headless),

View file

@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::unbounded_channel; use tokio::sync::mpsc::unbounded_channel;
use crate::{ use crate::{
JoecalService, ReceiveDialog, ReceiveRequest, TransferEvent, JocalService, ReceiveDialog, ReceiveRequest, TransferEvent,
error::{LocalSendError, Result}, error::{LocalSendError, Result},
models::{Device, FileMetadata}, models::{Device, FileMetadata},
}; };
@ -52,7 +52,7 @@ pub struct PrepareUploadRequest {
pub files: BTreeMap<String, FileMetadata>, pub files: BTreeMap<String, FileMetadata>,
} }
impl JoecalService { impl JocalService {
pub async fn prepare_upload( pub async fn prepare_upload(
&self, &self,
peer: &str, peer: &str,
@ -231,7 +231,7 @@ impl JoecalService {
} }
pub async fn prepare_upload( pub async fn prepare_upload(
State(service): State<JoecalService>, State(service): State<JocalService>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, ConnectInfo(addr): ConnectInfo<SocketAddr>,
Json(req): Json<PrepareUploadRequest>, Json(req): Json<PrepareUploadRequest>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@ -302,7 +302,7 @@ pub async fn prepare_upload(
pub async fn receive_upload( pub async fn receive_upload(
Query(params): Query<UploadParams>, Query(params): Query<UploadParams>,
State(service): State<JoecalService>, State(service): State<JocalService>,
body: Bytes, body: Bytes,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Extract query parameters // Extract query parameters
@ -379,7 +379,7 @@ pub struct UploadParams {
pub async fn register_cancel( pub async fn register_cancel(
Query(params): Query<CancelParams>, Query(params): Query<CancelParams>,
State(service): State<JoecalService>, State(service): State<JocalService>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let mut sessions_lock = service.sessions.lock().await; let mut sessions_lock = service.sessions.lock().await;
let session = match sessions_lock.get_mut(&params.session_id) { let session = match sessions_lock.get_mut(&params.session_id) {