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:
parent
c17d8cb715
commit
cd059a6552
3 changed files with 109 additions and 123 deletions
4
.rustfmt.toml
Normal file
4
.rustfmt.toml
Normal file
|
@ -0,0 +1,4 @@
|
|||
imports_granularity = "Crate"
|
||||
group_imports = "StdExternalCrate"
|
||||
wrap_comments = true
|
||||
edition = "2021"
|
102
src/lib.rs
102
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<u8>),
|
||||
}
|
||||
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<Vec<u8>>;
|
||||
pub type Sender = std::sync::mpsc::SyncSender<Vec<u8>>;
|
||||
|
||||
#[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<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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
126
src/main.rs
126
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<u8> = 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<Vec<u8>>),
|
||||
}
|
||||
|
||||
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<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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue