diff --git a/Cargo.toml b/Cargo.toml index 61cd231..cfdbe1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,12 @@ name = "jocalsend" version = "1.0.0" edition = "2024" +authors = ["Joe Ardent "] +keywords = ["p2p", "localsend", "tui", "linux"] +description = "A terminal implementation of the LocalSend protocol" +readme = "README.md" +license-file = "LICENSE.md" +repository = "https://git.kittencollective.com/nebkor/joecalsend" [dependencies] axum = { version = "0.8", features = ["macros"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..6954d7f --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# JocalSend, a TUI [LocalSend](https://github.com/localsend/localsend) implementation + +LocalSend is, in its words, "a free, open-source app that allows you to securely share files and +messages with nearby devices over your local network without needing an internet connection." It +comes in the form a Flutter/Dart cross-platform GUI application that runs on both mobile and desktop +devices. Using it on mobile is very nice, but the desktop experience is a bit lacking in zazz. + +JocalSend is an implementation of the [LocalSend protocol](https://github.com/localsend/protocol) +that uses [Ratatui](https://github.com/ratatui/ratatui) to provide an interactive terminal-based +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. + +## Capabilities and screenshots + +As with the official app, JocalSend can be used to send and receive files and text from other +LocalSend instances on your local subnetwork. Most of the modes have the following keybindings +available: + + - `M` -> go back to the main screen + - `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 + - `ESC` -> go back to the previous screen + - `Q` -> exit the application + +Additionally, 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) + +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 +A terminal implementation of the LocalSend protocol + +Usage: jocalsend [OPTIONS] + +Options: + -f, --file File to pre-select for sending + -t, --text Text string to send + -h, --help Print help + -V, --version Print version +``` + +### Sending Files + +JocalSend has a file-picking widget for selecting files to send: + +![./media/sending_file.png] + +but there's no preview available on the receiving side: + +![./media/receiving_file_from_jocalsend.png] + +### Sending text + +JocalSend supports entering text directly: + +![./media/sending_text.png] + +and on the receiving side in the official app, you see + +![./media/receiving_text_on_phone.png] + +### Receiving files + +The main screen shows incoming transfer requests: + +![./media/main_screen_receiving.png] + +hit `r` to enter the "receiving" screen to approve or deny: + +![./media/receiving_file_receiving_screen.png] + +### Receiving text + +If the incoming transfer request is plain text, JocalSend will show a preview in both the main +screen and in the receiving screen: + +![./media/receiving_text_main_screen.png] + +![./media/receiving_text_receive_screen.png] diff --git a/media/main_screen_receiving.png b/media/main_screen_receiving.png new file mode 100644 index 0000000..246da5f Binary files /dev/null and b/media/main_screen_receiving.png differ diff --git a/media/receiving_file_from_jocalsend.png b/media/receiving_file_from_jocalsend.png new file mode 100644 index 0000000..f34c2b9 Binary files /dev/null and b/media/receiving_file_from_jocalsend.png differ diff --git a/media/receiving_file_receiving_screen.png b/media/receiving_file_receiving_screen.png new file mode 100644 index 0000000..e6388d2 Binary files /dev/null and b/media/receiving_file_receiving_screen.png differ diff --git a/media/receiving_text_main_screen.png b/media/receiving_text_main_screen.png new file mode 100644 index 0000000..68010c5 Binary files /dev/null and b/media/receiving_text_main_screen.png differ diff --git a/media/receiving_text_on_phone.png b/media/receiving_text_on_phone.png new file mode 100644 index 0000000..b99e53d Binary files /dev/null and b/media/receiving_text_on_phone.png differ diff --git a/media/receiving_text_receive_screen.png b/media/receiving_text_receive_screen.png new file mode 100644 index 0000000..f32185c Binary files /dev/null and b/media/receiving_text_receive_screen.png differ diff --git a/media/sending_file.png b/media/sending_file.png new file mode 100644 index 0000000..acdf88a Binary files /dev/null and b/media/sending_file.png differ diff --git a/media/sending_text.png b/media/sending_text.png new file mode 100644 index 0000000..ff690f8 Binary files /dev/null and b/media/sending_text.png differ diff --git a/src/transfer.rs b/src/transfer.rs index 9d7b9b6..1724df3 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -111,27 +111,20 @@ impl JocalService { } pub async fn send_file(&self, peer: &str, file_path: PathBuf) -> Result<()> { - // Generate file metadata let file_metadata = FileMetadata::from_path(&file_path)?; - - // Prepare files map let mut files = BTreeMap::new(); files.insert(file_metadata.id.clone(), file_metadata.clone()); - // Prepare upload let prepare_response = self.prepare_upload(peer, files).await?; - // Get file token let token = prepare_response .files .get(&file_metadata.id) .ok_or(LocalSendError::InvalidToken)?; - // Read file contents let file_contents = tokio::fs::read(&file_path).await?; let bytes = Bytes::from(file_contents); - // Upload file self.send_bytes( &prepare_response.session_id, &file_metadata.id, @@ -144,17 +137,12 @@ impl JocalService { } pub async fn send_text(&self, peer: &str, text: &str) -> Result<()> { - // Generate file metadata let file_metadata = FileMetadata::from_text(text)?; - - // Prepare files map let mut files = BTreeMap::new(); files.insert(file_metadata.id.clone(), file_metadata.clone()); - // Prepare upload let prepare_response = self.prepare_upload(peer, files).await?; - // Get file token let token = prepare_response .files .get(&file_metadata.id) @@ -162,7 +150,6 @@ impl JocalService { let bytes = Bytes::from(text.to_owned()); - // Upload file self.send_bytes( &prepare_response.session_id, &file_metadata.id,