Dynamically choose static or streaming based on size.

Previously, all invocations with `-f`, to give a file as input, would default to streaming. Now,
though, it will try to statically turn it into a qr code, and failing that, will stream it as raptor
packets.
This commit is contained in:
Joe Ardent 2023-08-04 17:17:14 -07:00
parent c17d8cb715
commit cd059a6552
3 changed files with 109 additions and 123 deletions

4
.rustfmt.toml Normal file
View file

@ -0,0 +1,4 @@
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
wrap_comments = true
edition = "2021"

View file

@ -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}; use rkyv::{Archive, Deserialize, Serialize};
#[derive(Archive, Deserialize, Serialize, PartialEq, Eq, Clone)] pub const STREAMING_MTU: u16 = 776;
pub enum MessageData {
Text(String),
Data(Vec<u8>),
}
impl Debug for MessageData { pub type Receiver = std::sync::mpsc::Receiver<Vec<u8>>;
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { pub type Sender = std::sync::mpsc::SyncSender<Vec<u8>>;
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(),
}
}
}
#[derive(Archive, Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] #[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)]
pub struct Message { pub struct TxConfig {
pub len: u64,
pub mtu: u16,
pub description: String, pub description: String,
pub data: MessageData,
} }
impl Message { pub fn mk_img(bytes: &[u8]) -> RetainedImage {
pub fn new(data: MessageData, desc: Option<&str>) -> Self { let qr = fast_qr::QRBuilder::new(bytes).build().unwrap();
let description = if let Some(desc) = desc { let bytes = ImageBuilder::default()
desc.to_owned() .fit_width(1100)
} else { .to_pixmap(&qr)
"none".to_owned() .encode_png()
}; .unwrap();
Message { description, data } RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap()
}
pub fn stream_bytes(bytes: Vec<u8>, 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::<Vec<_>>();
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);
});
} }
} }

View file

@ -1,13 +1,8 @@
use std::{ffi::OsString, time::Duration}; use std::ffi::OsString;
use clap::Parser; use clap::Parser;
use cuttle::{stream_bytes, Content, Flasher};
use eframe::egui; 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)] #[derive(Parser, Debug)]
#[clap(author, version, trailing_var_arg = true)] #[clap(author, version, trailing_var_arg = true)]
@ -34,33 +29,24 @@ fn main() -> Result<(), eframe::Error> {
let cli = Cli::parse(); let cli = Cli::parse();
let flasher = if let Some(ref file) = cli.file { let description = if let Some(ref file) = cli.file {
let (tx, rx) = std::sync::mpsc::sync_channel(1); let text = cli.text().join(" ");
let content = Content::Streaming(rx); let sep = if text.is_empty() { "" } else { ": " };
let heading = if cli.text.is_empty() { let file = std::path::Path::new(&file)
file.to_string_lossy().to_string() .file_name()
} else { .unwrap_or_default()
cli.text().join(" ") .to_string_lossy();
}; format!("{file}{sep}{text}")
stream_file(file, tx, cli.rate);
Flasher {
heading,
content,
rate: cli.rate,
}
} else { } else {
let heading = "text message".to_string(); "text message".to_string()
let bytes: Vec<u8> = cli.text().join(" ").bytes().collect(); };
let bytes = bytes.leak();
let content = Content::Static(bytes);
Flasher { let content: Content = get_content(&cli);
heading,
content, let flasher = Flasher {
rate: cli.rate, heading: description,
} content,
rate: cli.rate,
}; };
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
@ -77,69 +63,19 @@ fn main() -> Result<(), eframe::Error> {
) )
} }
struct Flasher { fn get_content(cli: &Cli) -> Content {
pub heading: String, let bytes = if let Some(ref file) = cli.file {
pub content: Content, std::fs::read(file).unwrap_or_else(|e| panic!("tried to open {file:?}, got {e:?}"))
pub rate: u64, } else {
} cli.text().join(" ").bytes().collect()
};
enum Content { if bytes.len() < 1700 && fast_qr::QRBuilder::new(bytes.clone()).build().is_ok() {
Static(&'static [u8]), let bytes = bytes.leak();
Streaming(std::sync::mpsc::Receiver<Vec<u8>>), Content::Static(bytes)
} } else {
let (tx, rx) = std::sync::mpsc::sync_channel(2);
impl eframe::App for Flasher { stream_bytes(bytes, tx, cli.rate);
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { Content::Streaming(rx)
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);
});
} }
} }
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<Vec<u8>>, 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::<Vec<_>>();
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));
}
});
}