barcode-utils/src/main.rs
2025-09-30 22:47:05 +02:00

205 lines
5.5 KiB
Rust

use eframe::egui;
use eframe::egui::ImageSource;
use log::*;
use reqwest::Client;
use serde::Deserialize;
use serial::prelude::*;
use std::fs::File;
use std::io::{Cursor, copy};
use std::str::from_utf8;
use std::time::Duration;
use std::{env, fs};
use tokio;
use tokio::runtime::Runtime;
#[derive(Debug, Clone)]
struct BookData {
title: String,
authors: Vec<String>,
thumbnail: String,
description: String,
}
#[allow(unused)]
#[derive(Debug, Deserialize)]
struct BookRoot {
items: Vec<Item>,
}
#[allow(unused, non_snake_case)]
#[derive(Debug, Deserialize)]
struct Item {
volumeInfo: VolumeInfo,
}
#[allow(unused, non_snake_case)]
#[derive(Debug, Deserialize)]
struct VolumeInfo {
title: String,
authors: Option<Vec<String>>,
description: Option<String>,
imageLinks: Option<ImageLinks>,
}
#[allow(unused, non_snake_case)]
#[derive(Debug, Deserialize)]
struct ImageLinks {
thumbnail: Option<String>,
smallThumbnail: Option<String>,
}
fn main() -> eframe::Result {
env_logger::init();
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 880.0]),
..Default::default()
};
eframe::run_native(
"Barcode Scanner",
options,
Box::new(|cc| {
egui_extras::install_image_loaders(&cc.egui_ctx);
Ok(Box::<BarcodeScanner>::default())
}),
)
}
fn get_ean13_data() -> String {
let args: Vec<String> = env::args().collect();
let path_to_scanner = &args[1];
let mut port = serial::open(path_to_scanner).unwrap();
interact(&mut port).to_string()
}
fn interact<T: SerialPort>(port: &mut T) -> String {
port
.reconfigure(&|settings| {
settings.set_baud_rate(serial::Baud9600).unwrap();
settings.set_char_size(serial::Bits8);
settings.set_parity(serial::ParityNone);
settings.set_stop_bits(serial::Stop1);
settings.set_flow_control(serial::FlowNone);
Ok(())
})
.unwrap();
port.set_timeout(Duration::from_millis(1000000000)).unwrap();
let buf: &mut [u8; 14] = &mut [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
port.read_exact(buf).unwrap();
let barcode_id = from_utf8(buf).unwrap();
debug!("{}", &barcode_id);
barcode_id.to_string()
}
fn get_book_data(isbn: String) -> BookData {
let runtime = tokio::runtime::Runtime::new().unwrap();
let lookup_url = format!(
"https://www.googleapis.com/books/v1/volumes?q=isbn:{}",
isbn
);
let cleint = Client::new();
let book_data = runtime
.block_on(cleint.get(lookup_url).send())
.unwrap()
.json::<BookRoot>();
let book_data = runtime.block_on(book_data).unwrap();
let book_metadata: Vec<BookData> = book_data.items.into_iter().map(BookData::from).collect();
println!("{}", &book_metadata[0]);
return book_metadata[0].clone();
}
impl From<Item> for BookData {
fn from(item: Item) -> Self {
let info = item.volumeInfo;
BookData {
title: info.title,
authors: info.authors.unwrap_or_default(),
description: info.description.as_deref().unwrap_or("").to_string(),
thumbnail: info
.imageLinks
.as_ref()
.and_then(|links| links.thumbnail.as_deref())
.unwrap_or("")
.to_string(),
}
}
}
impl BookData {
fn new() -> BookData {
BookData {
title: "Unknown".to_string(),
authors: vec!["Unknown".to_string()],
thumbnail: "https://www.rust-lang.org/logos/rust-logo-512x512.png".to_string(),
description: "No description available".to_string(),
}
}
}
impl std::fmt::Display for BookData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Title: {}\nAuthors: {}\nDescription: {}\nThumbnail: {}",
self.title,
if self.authors.is_empty() {
"Unknown".to_string()
} else {
self.authors.join(", ")
},
if self.description.is_empty() {
"No description available".to_string()
} else {
self.description.clone()
},
self.thumbnail
)
}
}
fn get_thumbnail(url: String) {
let runtime = Runtime::new().unwrap();
let url = if url.is_empty() {
"https://www.rust-lang.org/logos/rust-logo-512x512.png".to_string()
} else {
url
};
fs::create_dir_all("cache").unwrap();
let mut dest = File::create("cache/image.jpeg").unwrap();
let image = runtime.block_on(reqwest::get(url)).unwrap();
let bytes = runtime.block_on(image.bytes()).unwrap();
let mut output = Cursor::new(bytes);
copy(&mut output, &mut dest).unwrap();
}
#[derive(Default)]
struct BarcodeScanner {}
impl eframe::App for BarcodeScanner {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) -> () {
egui_extras::install_image_loaders(ctx);
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Barcode Scanner");
ui.group(|ui| {
//loop {
let barcode = get_ean13_data();
ui.label(format!("EAN13: {}", barcode));
let book_metadata = if barcode.starts_with("978") {
get_book_data(barcode)
} else {
BookData::new()
};
get_thumbnail(book_metadata.thumbnail.clone());
ui.label(format!("Title: {}", book_metadata.title));
ui.label(format!("Description: {}", book_metadata.description));
ui.label(format!("Authors: {:?}", book_metadata.authors));
if !book_metadata.thumbnail.is_empty() {
ui.image(egui::ImageSource::Uri(book_metadata.thumbnail.into()))
} else {
ui.image(egui::ImageSource::Uri(std::borrow::Cow::Borrowed(
"https://www.rust-lang.org/logos/rust-logo-512x512.png",
)))
}
// }
})
});
}
}