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

View file

@ -1,12 +1,12 @@
[package]
name = "jocalsend"
# 1.61803398874989484
#--------^
version = "1.6.18033"
#-------^
version = "1.6.1803"
edition = "2024"
authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
keywords = ["p2p", "localsend", "tui", "linux"]
description = "A TUI for LocalSend"
description = "A terminal implementation of the LocalSend protocol"
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.3"
simsearch = "0.2"
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"] }

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.
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
nixpkgs, and so if you're a NixOS user, `nix-shell -p jocalsend` will do what you expect.
will probably work on Macs but if you're on a Mac, you probably have AirDrop.
## Capabilities and screenshots
@ -23,21 +22,18 @@ 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
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
- `T` -> enter text directly to send, `ESC` to cancel
- `/` -> fuzzy filename search, use `ESC` to stop inputting text
- `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)
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:
In addition to the interactive commands, it will also accept commandline arguments to pre-select a
file or pre-populate text to send:
```
$ jocalsend -h

View file

@ -1 +1 @@
1.618033
1.61803

View file

@ -15,18 +15,20 @@ pub(crate) struct FileFinder {
pub input: Input,
}
impl FileFinder {
pub fn new() -> Result<Self> {
let fuzzy = SimSearch::new_with(
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 {
pub fn new() -> Result<Self> {
Ok(Self {
explorer: FileExplorer::new()?,
fuzzy,
fuzzy: searcher(),
working_dir: None,
input: Default::default(),
})
@ -52,10 +54,14 @@ impl FileFinder {
}
pub fn reset_fuzzy(&mut self) {
self.fuzzy.clear();
self.clear_fuzzy();
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()
@ -63,7 +69,7 @@ impl FileFinder {
return;
}
self.working_dir = Some(self.cwd().to_path_buf());
self.reset_fuzzy();
self.clear_fuzzy();
for (i, f) in self.explorer.files().iter().enumerate() {
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) {
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()]),
@ -264,7 +266,7 @@ fn help_screen(area: Rect, buf: &mut Buffer) {
]),
// Receiving
Row::new(vec![
"Manage incoming transfer requests"
"Manage incoming data requests (receive data)"
.bold()
.into_right_aligned_line(),
spacer.clone(),
@ -278,47 +280,34 @@ fn help_screen(area: Rect, buf: &mut Buffer) {
spacer.clone(),
"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
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
Row::new(vec![
"Quit the application".bold().into_right_aligned_line(),
spacer.clone(),
"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![
Constraint::Max(3),
Constraint::Max(12),
Constraint::Max(3),
])
.flex(Flex::SpaceAround);
let [intro_area, bindings_area, outro_area] = layout.areas(area);
let layout = Layout::vertical(vec![Constraint::Max(3), Constraint::Min(1)]);
let [intro_area, bindings_area] = layout.areas(area);
let widths = vec![
Constraint::Percentage(50),
@ -333,15 +322,8 @@ 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) {

View file

@ -131,6 +131,7 @@ 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(),
@ -143,15 +144,6 @@ 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 {
@ -281,7 +273,11 @@ pub async fn handle_receive_upload(
let file_metadata = match session.files.get(file_id) {
Some(metadata) => metadata,
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
if let Err(e) = tokio::fs::create_dir_all(download_dir).await {
log::error!("could not create download directory '{download_dir:?}', got {e}");
return (StatusCode::INTERNAL_SERVER_ERROR, "could not save content").into_response();
return (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to create directory: {e}"),
)
.into_response();
}
// Create file path
@ -298,14 +297,13 @@ pub async fn handle_receive_upload(
// Write file
if let Err(e) = tokio::fs::write(&file_path, body).await {
log::warn!("could not save content to {file_path:?}, got {e}");
return (StatusCode::INTERNAL_SERVER_ERROR, "could not save content").into_response();
return (
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) {
service.send_event(JocalEvent::ReceivedInbound(id));
};