From 77d44b886812313d0c629c9a1bf6dfde51fe6c27 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Thu, 21 Aug 2025 12:09:33 -0700 Subject: [PATCH] update simsearch, fill out help screen --- Cargo.lock | 20 +++++++----------- Cargo.toml | 4 ++-- README.md | 18 ++++++++++------- src/app/file_finder.rs | 26 +++++++++--------------- src/app/widgets.rs | 46 +++++++++++++++++++++++++++++------------- src/transfer.rs | 34 ++++++++++++++++--------------- 6 files changed, 80 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e969030..ea9cf63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,7 +414,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] @@ -545,7 +545,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.106", ] @@ -2281,11 +2281,11 @@ dependencies = [ [[package]] name = "simsearch" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c869b25830e4824ef7279015cfc298a0674aca6a54eeff2efce8d12bf3701fe" +checksum = "629d21c4ebf25655995cda9eb93e85539fa68b0438acb85e9e5d10f6fe2404bc" dependencies = [ - "strsim 0.10.0", + "strsim", "triple_accel", ] @@ -2323,12 +2323,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -2699,9 +2693,9 @@ dependencies = [ [[package]] name = "triple_accel" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622b09ce2fe2df4618636fb92176d205662f59803f39e70d1c333393082de96c" +checksum = "22048bc95dfb2ffd05b1ff9a756290a009224b60b2f0e7525faeee7603851e63" [[package]] name = "try-lock" diff --git a/Cargo.toml b/Cargo.toml index a7e0563..09d3f92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ version = "1.6.1803" edition = "2024" authors = ["Joe Ardent "] keywords = ["p2p", "localsend", "tui", "linux"] -description = "A terminal implementation of the LocalSend protocol" +description = "A TUI for LocalSend" readme = "README.md" license-file = "LICENSE.md" repository = "https://git.kittencollective.com/nebkor/joecalsend" @@ -34,7 +34,7 @@ rustix = { version = "1", default-features = false, features = ["system"] } serde = { version = "1", features = ["derive"] } serde_json = "1" sha256 = "1.6" -simsearch = "0.2" +simsearch = "0.3" thiserror = "2" tokio = { version = "1", default-features = false, features = ["time", "macros", "rt-multi-thread"] } tokio-rustls = { version = "0.26", default-features = false, features = ["tls12", "logging"] } diff --git a/README.md b/README.md index 13315fa..38ae9a6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ that uses [Ratatui](https://github.com/ratatui/ratatui) to provide an interactiv application, and is compatible with the official app. Install with `cargo install jocalsend` (requires [Rust](https://rustup.rs/)); tested on Linux, it -will probably work on Macs but if you're on a Mac, you probably have AirDrop. +will probably work on Macs but if you're on a Mac, you probably have AirDrop. It's also available in +nixpkgs, and so if you're a NixOS user, `nix-shell -p jocalsend` will do what you expect. ## Capabilities and screenshots @@ -22,18 +23,21 @@ available: - `S` -> go to the sending screen, defaulting to sending files - `R` -> go to the receiving screen to approve or deny incoming transfers - `L` -> go to the logging screen where you can adjust the log level + - `C` -> clear the list of local peers and re-discover them + - `H` or `?` -> go to help screen - `ESC` -> go back to the previous screen - `Q` -> exit the application -Additionally, when in the sending screen, the following are available +When in the sending screen, the following are available - `TAB` -> switch between content selection and peer selection - - `P` -> switch to peer selection - - `T` -> switch to entering text to send - - `F` -> switch to selecting files to send (not available when entering text, use `ESC` to exit text entry) + - `T` -> enter text directly to send, `ESC` to cancel + - `/` -> fuzzy filename search, use `ESC` to stop inputting text -In addition to the interactive commands, it will also accept commandline arguments to pre-select a -file or pre-populate text to send: +When in the receiving screen, use `A` to approve the incoming transfer request, or `D` to deny it. + +Finally, it will also accept commandline arguments to pre-select a file or pre-populate text to +send: ``` $ jocalsend -h diff --git a/src/app/file_finder.rs b/src/app/file_finder.rs index 75d2682..2d491fb 100644 --- a/src/app/file_finder.rs +++ b/src/app/file_finder.rs @@ -15,20 +15,18 @@ pub(crate) struct FileFinder { pub input: Input, } -fn searcher() -> SimSearch { - SimSearch::new_with( - SearchOptions::new() - .stop_words(vec![std::path::MAIN_SEPARATOR_STR.to_string()]) - .stop_whitespace(false) - .threshold(0.0), - ) -} - impl FileFinder { pub fn new() -> Result { + let fuzzy = SimSearch::new_with( + SearchOptions::new() + .stop_words(vec![std::path::MAIN_SEPARATOR_STR.to_string()]) + .stop_whitespace(false) + .threshold(0.0), + ); + Ok(Self { explorer: FileExplorer::new()?, - fuzzy: searcher(), + fuzzy, working_dir: None, input: Default::default(), }) @@ -54,14 +52,10 @@ impl FileFinder { } pub fn reset_fuzzy(&mut self) { - self.clear_fuzzy(); + self.fuzzy.clear(); self.input.reset(); } - fn clear_fuzzy(&mut self) { - self.fuzzy = searcher(); - } - pub fn index(&mut self) { if let Some(owd) = self.working_dir.as_ref() && owd == self.cwd() @@ -69,7 +63,7 @@ impl FileFinder { return; } self.working_dir = Some(self.cwd().to_path_buf()); - self.clear_fuzzy(); + self.reset_fuzzy(); for (i, f) in self.explorer.files().iter().enumerate() { self.fuzzy.insert(i, f.name()); diff --git a/src/app/widgets.rs b/src/app/widgets.rs index 654cba5..3f56305 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -253,8 +253,6 @@ fn outer_frame(screen: &CurrentScreen, menu: &Line, area: Rect, buf: &mut Buffer } fn help_screen(area: Rect, buf: &mut Buffer) { - let intro = "JocalSend is a mode-based application that responds to key-presses. Most modes support the following key bindings:".to_line().centered(); - let intro = Paragraph::new(intro).wrap(Wrap { trim: true }); let spacer = "".to_line().centered(); let main_bindings = vec![ Row::new(vec!["".to_line(), spacer.clone(), "".to_line()]), @@ -266,7 +264,7 @@ fn help_screen(area: Rect, buf: &mut Buffer) { ]), // Receiving Row::new(vec![ - "Manage incoming data requests (receive data)" + "Manage incoming transfer requests" .bold() .into_right_aligned_line(), spacer.clone(), @@ -280,23 +278,19 @@ fn help_screen(area: Rect, buf: &mut Buffer) { spacer.clone(), "L".bold().into_left_aligned_line(), ]), - // misc: pop - Row::new(vec![ - "Go to previous screen".bold().into_right_aligned_line(), - spacer.clone(), - "ESC".bold().into_left_aligned_line(), - ]), // misc: main menu Row::new(vec![ "Go to the main screen".bold().into_right_aligned_line(), spacer.clone(), "M".bold().into_left_aligned_line(), ]), - // misc: quit + // misc: clear peers Row::new(vec![ - "Quit the application".bold().into_right_aligned_line(), + "Clear peers and rediscover" + .bold() + .into_right_aligned_line(), spacer.clone(), - "Q".bold().into_left_aligned_line(), + "C".bold().into_left_aligned_line(), ]), // misc: help Row::new(vec![ @@ -304,10 +298,27 @@ fn help_screen(area: Rect, buf: &mut Buffer) { spacer.clone(), "H or ?".bold().into_left_aligned_line(), ]), + // misc: pop + Row::new(vec![ + "Go to previous screen".bold().into_right_aligned_line(), + spacer.clone(), + "ESC".bold().into_left_aligned_line(), + ]), + // misc: quit + Row::new(vec![ + "Quit the application".bold().into_right_aligned_line(), + spacer.clone(), + "Q".bold().into_left_aligned_line(), + ]), ]; - let layout = Layout::vertical(vec![Constraint::Max(3), Constraint::Min(1)]); - let [intro_area, bindings_area] = layout.areas(area); + let layout = Layout::vertical(vec![ + Constraint::Max(3), + Constraint::Max(12), + Constraint::Max(3), + ]) + .flex(Flex::SpaceAround); + let [intro_area, bindings_area, outro_area] = layout.areas(area); let widths = vec![ Constraint::Percentage(50), @@ -322,8 +333,15 @@ fn help_screen(area: Rect, buf: &mut Buffer) { Clear.render(area, buf); + let intro = "JocalSend is a mode-based application that responds to key-presses. Most modes support the following key bindings:".to_line().centered(); + let intro = Paragraph::new(intro).wrap(Wrap { trim: true }); + + let outro = "Additional key bindings are available when in the sending or receiving screens, and are displayed at the bottom of the screen there.".to_line().centered(); + let outro = Paragraph::new(outro).wrap(Wrap { trim: true }); + intro.render(intro_area, buf); main_bindings.render(bindings_area, buf); + outro.render(outro_area, buf); } fn logger(area: Rect, buf: &mut Buffer) { diff --git a/src/transfer.rs b/src/transfer.rs index 0cd62a1..a8658c5 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -131,7 +131,6 @@ impl JocalService { let token = match prepare_response.files.get(&metadata.id) { Some(t) => t, None => { - log::warn!(""); send_tx( JocalEvent::SendFailed { error: "missing token in prepare response from remote".into(), @@ -144,6 +143,15 @@ impl JocalService { let content_id = &metadata.id; let session_id = prepare_response.session_id; + log::info!( + "sending {content_id} to {}", + peers + .lock() + .await + .get(&peer) + .map(|(_, peer)| peer.alias.as_str()) + .unwrap_or("unknown peer") + ); let resp = do_send_bytes(sessions, client, &session_id, content_id, token, bytes).await; match resp { @@ -273,11 +281,7 @@ pub async fn handle_receive_upload( let file_metadata = match session.files.get(file_id) { Some(metadata) => metadata, None => { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - "File not found".to_string(), - ) - .into_response(); + return (StatusCode::BAD_REQUEST, "File not found".to_string()).into_response(); } }; @@ -285,11 +289,8 @@ pub async fn handle_receive_upload( // Create directory if it doesn't exist if let Err(e) = tokio::fs::create_dir_all(download_dir).await { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to create directory: {e}"), - ) - .into_response(); + log::error!("could not create download directory '{download_dir:?}', got {e}"); + return (StatusCode::INTERNAL_SERVER_ERROR, "could not save content").into_response(); } // Create file path @@ -297,13 +298,14 @@ pub async fn handle_receive_upload( // Write file if let Err(e) = tokio::fs::write(&file_path, body).await { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to write file: {e}"), - ) - .into_response(); + log::warn!("could not save content to {file_path:?}, got {e}"); + return (StatusCode::INTERNAL_SERVER_ERROR, "could not save content").into_response(); } + log::info!( + "saved content from {} to {file_path:?}", + &session.sender.alias + ); if let Ok(id) = Julid::from_str(session_id) { service.send_event(JocalEvent::ReceivedInbound(id)); };