diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..4c8d0e1 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,4 @@ +imports_granularity = "Crate" +group_imports = "StdExternalCrate" +wrap_comments = true +edition = "2021" diff --git a/src/lib.rs b/src/lib.rs index ac267f2..f04733d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,39 +1,85 @@ -use std::fmt::Debug; +use std::{fmt::Debug, time::Duration}; +use eframe::egui; +use egui_extras::RetainedImage; +use fast_qr::convert::image::ImageBuilder; +use rand::seq::SliceRandom; +use raptorq::{Encoder, ObjectTransmissionInformation}; use rkyv::{Archive, Deserialize, Serialize}; -#[derive(Archive, Deserialize, Serialize, PartialEq, Eq, Clone)] -pub enum MessageData { - Text(String), - Data(Vec), -} +pub const STREAMING_MTU: u16 = 776; -impl Debug for MessageData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Text(msg_txt) => f.debug_tuple("Text").field(msg_txt).finish(), - Self::Data(data) => f - .debug_tuple("Data") - .field(&format!("{} bytes", data.len())) - .finish(), - } - } -} +pub type Receiver = std::sync::mpsc::Receiver>; +pub type Sender = std::sync::mpsc::SyncSender>; -#[derive(Archive, Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] -pub struct Message { +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +pub struct TxConfig { + pub len: u64, + pub mtu: u16, pub description: String, - pub data: MessageData, } -impl Message { - pub fn new(data: MessageData, desc: Option<&str>) -> Self { - let description = if let Some(desc) = desc { - desc.to_owned() - } else { - "none".to_owned() - }; +pub fn mk_img(bytes: &[u8]) -> RetainedImage { + let qr = fast_qr::QRBuilder::new(bytes).build().unwrap(); + let bytes = ImageBuilder::default() + .fit_width(1100) + .to_pixmap(&qr) + .encode_png() + .unwrap(); - Message { description, data } + RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap() +} + +pub fn stream_bytes(bytes: Vec, tx: Sender, rate: u64) { + let sleep = (1000.0 / rate as f64) as u64; + + std::thread::spawn(move || { + let rng = &mut rand::thread_rng(); + let config = + ObjectTransmissionInformation::with_defaults(bytes.len() as u64, STREAMING_MTU); + let encoder = Encoder::new(&bytes, config); + let mut packets = encoder + .get_encoded_packets(10) + .iter() + .map(|p| p.serialize()) + .collect::>(); + packets.shuffle(rng); + + for (_counter, packet) in packets.iter().cycle().enumerate() { + tx.send(packet.clone()).unwrap(); + std::thread::sleep(Duration::from_millis(sleep)); + } + }); +} + +pub struct Flasher { + pub heading: String, + pub content: Content, + pub rate: u64, +} + +pub enum Content { + Static(&'static [u8]), + Streaming(Receiver), +} + +impl eframe::App for Flasher { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + let sleep = 1000.0 / self.rate as f64; + ctx.request_repaint_after(Duration::from_millis(sleep as u64)); + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading(&self.heading); + let img = match self.content { + Content::Static(bytes) => mk_img(bytes), + Content::Streaming(ref rx) => { + if let Ok(bytes) = rx.try_recv() { + mk_img(&bytes) + } else { + return; + } + } + }; + img.show(ui); + }); } } diff --git a/src/main.rs b/src/main.rs index 79f341f..0e8465f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,8 @@ -use std::{ffi::OsString, time::Duration}; +use std::ffi::OsString; use clap::Parser; +use cuttle::{stream_bytes, Content, Flasher}; use eframe::egui; -use egui_extras::RetainedImage; -use fast_qr::convert::image::ImageBuilder; -use rand::seq::SliceRandom; -use raptorq::{Encoder, ObjectTransmissionInformation}; - -pub const MTU: u16 = 776; #[derive(Parser, Debug)] #[clap(author, version, trailing_var_arg = true)] @@ -34,33 +29,24 @@ fn main() -> Result<(), eframe::Error> { let cli = Cli::parse(); - let flasher = if let Some(ref file) = cli.file { - let (tx, rx) = std::sync::mpsc::sync_channel(1); - let content = Content::Streaming(rx); - let heading = if cli.text.is_empty() { - file.to_string_lossy().to_string() - } else { - cli.text().join(" ") - }; - - stream_file(file, tx, cli.rate); - - Flasher { - heading, - content, - rate: cli.rate, - } + let description = if let Some(ref file) = cli.file { + let text = cli.text().join(" "); + let sep = if text.is_empty() { "" } else { ": " }; + let file = std::path::Path::new(&file) + .file_name() + .unwrap_or_default() + .to_string_lossy(); + format!("{file}{sep}{text}") } else { - let heading = "text message".to_string(); - let bytes: Vec = cli.text().join(" ").bytes().collect(); - let bytes = bytes.leak(); - let content = Content::Static(bytes); + "text message".to_string() + }; - Flasher { - heading, - content, - rate: cli.rate, - } + let content: Content = get_content(&cli); + + let flasher = Flasher { + heading: description, + content, + rate: cli.rate, }; let options = eframe::NativeOptions { @@ -77,69 +63,19 @@ fn main() -> Result<(), eframe::Error> { ) } -struct Flasher { - pub heading: String, - pub content: Content, - pub rate: u64, -} +fn get_content(cli: &Cli) -> Content { + let bytes = if let Some(ref file) = cli.file { + std::fs::read(file).unwrap_or_else(|e| panic!("tried to open {file:?}, got {e:?}")) + } else { + cli.text().join(" ").bytes().collect() + }; -enum Content { - Static(&'static [u8]), - Streaming(std::sync::mpsc::Receiver>), -} - -impl eframe::App for Flasher { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - let sleep = 1000.0 / self.rate as f64; - ctx.request_repaint_after(Duration::from_millis(sleep as u64)); - egui::CentralPanel::default().show(ctx, |ui| { - ui.heading(&self.heading); - let img = match self.content { - Content::Static(bytes) => mk_img(bytes), - Content::Streaming(ref rx) => { - if let Ok(bytes) = rx.try_recv() { - mk_img(&bytes) - } else { - return; - } - } - }; - img.show(ui); - }); + if bytes.len() < 1700 && fast_qr::QRBuilder::new(bytes.clone()).build().is_ok() { + let bytes = bytes.leak(); + Content::Static(bytes) + } else { + let (tx, rx) = std::sync::mpsc::sync_channel(2); + stream_bytes(bytes, tx, cli.rate); + Content::Streaming(rx) } } - -fn mk_img(bytes: &[u8]) -> RetainedImage { - let qr = fast_qr::QRBuilder::new(bytes).build().unwrap(); - let bytes = ImageBuilder::default() - .fit_width(1100) - .to_pixmap(&qr) - .encode_png() - .unwrap(); - - RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap() -} - -fn stream_file(file: &OsString, tx: std::sync::mpsc::SyncSender>, rate: u64) { - let bytes = std::fs::read(file).unwrap_or_else(|e| panic!("tried to open {file:?}, got {e:?}")); - - let sleep = (1000.0 / rate as f64) as u64; - - std::thread::spawn(move || { - let rng = &mut rand::thread_rng(); - let config = ObjectTransmissionInformation::with_defaults(bytes.len() as u64, MTU); - let encoder = Encoder::new(&bytes, config); - let mut packets = encoder - .get_encoded_packets(10) - .iter() - .map(|p| p.serialize()) - .collect::>(); - packets.shuffle(rng); - dbg!(&packets.len()); - - for (_counter, packet) in packets.iter().cycle().enumerate() { - tx.send(packet.clone()).unwrap(); - std::thread::sleep(Duration::from_millis(sleep)); - } - }); -}