Compare commits

..

No commits in common. "04b6388b2c70f19303763034395c021a15386ae2" and "2b1e0f7cb8aac383d2f366e40549ea1556f0cf0e" have entirely different histories.

7 changed files with 75 additions and 87 deletions

22
Cargo.lock generated
View file

@ -414,7 +414,7 @@ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"clap_lex", "clap_lex",
"strsim", "strsim 0.11.1",
] ]
[[package]] [[package]]
@ -545,7 +545,7 @@ dependencies = [
"ident_case", "ident_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim 0.11.1",
"syn 2.0.106", "syn 2.0.106",
] ]
@ -1326,7 +1326,7 @@ dependencies = [
[[package]] [[package]]
name = "jocalsend" name = "jocalsend"
version = "1.6.18033" version = "1.6.1803"
dependencies = [ dependencies = [
"axum", "axum",
"axum-server", "axum-server",
@ -2281,11 +2281,11 @@ dependencies = [
[[package]] [[package]]
name = "simsearch" name = "simsearch"
version = "0.3.0" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "629d21c4ebf25655995cda9eb93e85539fa68b0438acb85e9e5d10f6fe2404bc" checksum = "9c869b25830e4824ef7279015cfc298a0674aca6a54eeff2efce8d12bf3701fe"
dependencies = [ dependencies = [
"strsim", "strsim 0.10.0",
"triple_accel", "triple_accel",
] ]
@ -2323,6 +2323,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"
@ -2693,9 +2699,9 @@ dependencies = [
[[package]] [[package]]
name = "triple_accel" name = "triple_accel"
version = "0.4.0" version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22048bc95dfb2ffd05b1ff9a756290a009224b60b2f0e7525faeee7603851e63" checksum = "622b09ce2fe2df4618636fb92176d205662f59803f39e70d1c333393082de96c"
[[package]] [[package]]
name = "try-lock" name = "try-lock"

View file

@ -1,12 +1,12 @@
[package] [package]
name = "jocalsend" name = "jocalsend"
# 1.61803398874989484 # 1.61803398874989484
#--------^ #-------^
version = "1.6.18033" version = "1.6.1803"
edition = "2024" edition = "2024"
authors = ["Joe Ardent <code@ardent.nebcorp.com>"] authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
keywords = ["p2p", "localsend", "tui", "linux"] keywords = ["p2p", "localsend", "tui", "linux"]
description = "A TUI for LocalSend" description = "A terminal implementation of the LocalSend protocol"
readme = "README.md" readme = "README.md"
license-file = "LICENSE.md" license-file = "LICENSE.md"
repository = "https://git.kittencollective.com/nebkor/joecalsend" 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 = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
sha256 = "1.6" sha256 = "1.6"
simsearch = "0.3" simsearch = "0.2"
thiserror = "2" thiserror = "2"
tokio = { version = "1", default-features = false, features = ["time", "macros", "rt-multi-thread"] } tokio = { version = "1", default-features = false, features = ["time", "macros", "rt-multi-thread"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["tls12", "logging"] } tokio-rustls = { version = "0.26", default-features = false, features = ["tls12", "logging"] }

View file

@ -10,8 +10,7 @@ that uses [Ratatui](https://github.com/ratatui/ratatui) to provide an interactiv
application, and is compatible with the official app. application, and is compatible with the official app.
Install with `cargo install jocalsend` (requires [Rust](https://rustup.rs/)); tested on Linux, it 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. It's also available in will probably work on Macs but if you're on a Mac, you probably have AirDrop.
nixpkgs, and so if you're a NixOS user, `nix-shell -p jocalsend` will do what you expect.
## Capabilities and screenshots ## Capabilities and screenshots
@ -23,21 +22,18 @@ available:
- `S` -> go to the sending screen, defaulting to sending files - `S` -> go to the sending screen, defaulting to sending files
- `R` -> go to the receiving screen to approve or deny incoming transfers - `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 - `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 - `ESC` -> go back to the previous screen
- `Q` -> exit the application - `Q` -> exit the application
When in the sending screen, the following are available Additionally, when in the sending screen, the following are available
- `TAB` -> switch between content selection and peer selection - `TAB` -> switch between content selection and peer selection
- `T` -> enter text directly to send, `ESC` to cancel - `P` -> switch to peer selection
- `/` -> fuzzy filename search, use `ESC` to stop inputting text - `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)
When in the receiving screen, use `A` to approve the incoming transfer request, or `D` to deny it. In addition to the interactive commands, it will also accept commandline arguments to pre-select a
file or pre-populate text to send:
Finally, it will also accept commandline arguments to pre-select a file or pre-populate text to
send:
``` ```
$ jocalsend -h $ jocalsend -h

View file

@ -1 +1 @@
1.618033 1.61803

View file

@ -15,18 +15,20 @@ pub(crate) struct FileFinder {
pub input: Input, pub input: Input,
} }
fn searcher() -> SimSearch<usize> {
SimSearch::new_with(
SearchOptions::new()
.stop_words(vec![std::path::MAIN_SEPARATOR_STR.to_string()])
.stop_whitespace(false)
.threshold(0.0),
)
}
impl FileFinder { impl FileFinder {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
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 { Ok(Self {
explorer: FileExplorer::new()?, explorer: FileExplorer::new()?,
fuzzy, fuzzy: searcher(),
working_dir: None, working_dir: None,
input: Default::default(), input: Default::default(),
}) })
@ -52,10 +54,14 @@ impl FileFinder {
} }
pub fn reset_fuzzy(&mut self) { pub fn reset_fuzzy(&mut self) {
self.fuzzy.clear(); self.clear_fuzzy();
self.input.reset(); self.input.reset();
} }
fn clear_fuzzy(&mut self) {
self.fuzzy = searcher();
}
pub fn index(&mut self) { pub fn index(&mut self) {
if let Some(owd) = self.working_dir.as_ref() if let Some(owd) = self.working_dir.as_ref()
&& owd == self.cwd() && owd == self.cwd()
@ -63,7 +69,7 @@ impl FileFinder {
return; return;
} }
self.working_dir = Some(self.cwd().to_path_buf()); self.working_dir = Some(self.cwd().to_path_buf());
self.reset_fuzzy(); self.clear_fuzzy();
for (i, f) in self.explorer.files().iter().enumerate() { for (i, f) in self.explorer.files().iter().enumerate() {
self.fuzzy.insert(i, f.name()); self.fuzzy.insert(i, f.name());

View file

@ -253,6 +253,8 @@ fn outer_frame(screen: &CurrentScreen, menu: &Line, area: Rect, buf: &mut Buffer
} }
fn help_screen(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 spacer = "".to_line().centered();
let main_bindings = vec![ let main_bindings = vec![
Row::new(vec!["".to_line(), spacer.clone(), "".to_line()]), Row::new(vec!["".to_line(), spacer.clone(), "".to_line()]),
@ -264,7 +266,7 @@ fn help_screen(area: Rect, buf: &mut Buffer) {
]), ]),
// Receiving // Receiving
Row::new(vec![ Row::new(vec![
"Manage incoming transfer requests" "Manage incoming data requests (receive data)"
.bold() .bold()
.into_right_aligned_line(), .into_right_aligned_line(),
spacer.clone(), spacer.clone(),
@ -278,47 +280,34 @@ fn help_screen(area: Rect, buf: &mut Buffer) {
spacer.clone(), spacer.clone(),
"L".bold().into_left_aligned_line(), "L".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: clear peers
Row::new(vec![
"Clear peers and rediscover"
.bold()
.into_right_aligned_line(),
spacer.clone(),
"C".bold().into_left_aligned_line(),
]),
// misc: help
Row::new(vec![
"This help screen".bold().into_right_aligned_line(),
spacer.clone(),
"H or ?".bold().into_left_aligned_line(),
]),
// misc: pop // misc: pop
Row::new(vec![ Row::new(vec![
"Go to previous screen".bold().into_right_aligned_line(), "Go to previous screen".bold().into_right_aligned_line(),
spacer.clone(), spacer.clone(),
"ESC".bold().into_left_aligned_line(), "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: quit
Row::new(vec![ Row::new(vec![
"Quit the application".bold().into_right_aligned_line(), "Quit the application".bold().into_right_aligned_line(),
spacer.clone(), spacer.clone(),
"Q".bold().into_left_aligned_line(), "Q".bold().into_left_aligned_line(),
]), ]),
// misc: help
Row::new(vec![
"This help screen".bold().into_right_aligned_line(),
spacer.clone(),
"H or ?".bold().into_left_aligned_line(),
]),
]; ];
let layout = Layout::vertical(vec![ let layout = Layout::vertical(vec![Constraint::Max(3), Constraint::Min(1)]);
Constraint::Max(3), let [intro_area, bindings_area] = layout.areas(area);
Constraint::Max(12),
Constraint::Max(3),
])
.flex(Flex::SpaceAround);
let [intro_area, bindings_area, outro_area] = layout.areas(area);
let widths = vec![ let widths = vec![
Constraint::Percentage(50), Constraint::Percentage(50),
@ -333,15 +322,8 @@ fn help_screen(area: Rect, buf: &mut Buffer) {
Clear.render(area, buf); 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); intro.render(intro_area, buf);
main_bindings.render(bindings_area, buf); main_bindings.render(bindings_area, buf);
outro.render(outro_area, buf);
} }
fn logger(area: Rect, buf: &mut Buffer) { fn logger(area: Rect, buf: &mut Buffer) {

View file

@ -131,6 +131,7 @@ impl JocalService {
let token = match prepare_response.files.get(&metadata.id) { let token = match prepare_response.files.get(&metadata.id) {
Some(t) => t, Some(t) => t,
None => { None => {
log::warn!("");
send_tx( send_tx(
JocalEvent::SendFailed { JocalEvent::SendFailed {
error: "missing token in prepare response from remote".into(), error: "missing token in prepare response from remote".into(),
@ -143,15 +144,6 @@ impl JocalService {
let content_id = &metadata.id; let content_id = &metadata.id;
let session_id = prepare_response.session_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; let resp = do_send_bytes(sessions, client, &session_id, content_id, token, bytes).await;
match resp { match resp {
@ -281,7 +273,11 @@ pub async fn handle_receive_upload(
let file_metadata = match session.files.get(file_id) { let file_metadata = match session.files.get(file_id) {
Some(metadata) => metadata, Some(metadata) => metadata,
None => { None => {
return (StatusCode::BAD_REQUEST, "File not found".to_string()).into_response(); return (
StatusCode::INTERNAL_SERVER_ERROR,
"File not found".to_string(),
)
.into_response();
} }
}; };
@ -289,8 +285,11 @@ pub async fn handle_receive_upload(
// Create directory if it doesn't exist // Create directory if it doesn't exist
if let Err(e) = tokio::fs::create_dir_all(download_dir).await { if let Err(e) = tokio::fs::create_dir_all(download_dir).await {
log::error!("could not create download directory '{download_dir:?}', got {e}"); return (
return (StatusCode::INTERNAL_SERVER_ERROR, "could not save content").into_response(); StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to create directory: {e}"),
)
.into_response();
} }
// Create file path // Create file path
@ -298,14 +297,13 @@ pub async fn handle_receive_upload(
// Write file // Write file
if let Err(e) = tokio::fs::write(&file_path, body).await { if let Err(e) = tokio::fs::write(&file_path, body).await {
log::warn!("could not save content to {file_path:?}, got {e}"); return (
return (StatusCode::INTERNAL_SERVER_ERROR, "could not save content").into_response(); StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to write file: {e}"),
)
.into_response();
} }
log::info!(
"saved content from {} to {file_path:?}",
&session.sender.alias
);
if let Ok(id) = Julid::from_str(session_id) { if let Ok(id) = Julid::from_str(session_id) {
service.send_event(JocalEvent::ReceivedInbound(id)); service.send_event(JocalEvent::ReceivedInbound(id));
}; };