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};
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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 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));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue