Compare commits
3 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
235b66d582 | ||
|
|
80f9037f42 | ||
|
|
4361508419 |
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
||||||
/target
|
/target
|
||||||
/src-tauri
|
/src-tauri
|
||||||
/dist
|
/dist
|
||||||
|
/cache
|
||||||
|
/.zed
|
||||||
|
|
|
||||||
4591
Cargo.lock
generated
4591
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,4 +9,10 @@ serial = "0.4.0"
|
||||||
reqwest = { version = "0.12.23", features = ["json"] }
|
reqwest = { version = "0.12.23", features = ["json"] }
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
tokio = { version = "1.47.1", features = ["full"] }
|
tokio = { version = "1.47.1", features = ["full"] }
|
||||||
serde = { version = "1.0.227", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
eframe = "0.32.3"
|
||||||
|
egui_extras = { version = "0.32.3", features = ["all_loaders"] }
|
||||||
|
log = "0.4.28"
|
||||||
|
env_logger = "0.11.8"
|
||||||
|
image = "0.25.8"
|
||||||
|
egui_commonmark = "0.21.1"
|
||||||
|
|
|
||||||
BIN
src/cache/img_name.webp
vendored
Normal file
BIN
src/cache/img_name.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
14
src/image.rs
Normal file
14
src/image.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use image::ImageFormat;
|
||||||
|
use image::ImageReader;
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
pub fn convert_to_webp(img_name: String) {
|
||||||
|
debug!("The img_name is: {img_name}");
|
||||||
|
let img = ImageReader::open(format!("cache/{img_name}.jpeg"))
|
||||||
|
.unwrap()
|
||||||
|
.decode()
|
||||||
|
.unwrap();
|
||||||
|
img
|
||||||
|
.save_with_format(format!("src/cache/img_name.webp"), ImageFormat::WebP)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
286
src/main.rs
286
src/main.rs
|
|
@ -1,129 +1,245 @@
|
||||||
|
use eframe::egui;
|
||||||
|
use log::*;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serial::prelude::*;
|
use serial::prelude::*;
|
||||||
use std::env;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io::Read;
|
||||||
|
use std::io::{Cursor, copy};
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
|
use std::sync::mpsc::{self, Receiver, Sender};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::{env, fs};
|
||||||
|
use tokio;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
pub mod image;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
struct BookData {
|
struct BookData {
|
||||||
title: String,
|
title: String,
|
||||||
authors: Vec<String>,
|
authors: Vec<String>,
|
||||||
thumbnail: String,
|
thumbnail: String,
|
||||||
description: String,
|
description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct BookRoot {
|
struct BookRoot {
|
||||||
items: Vec<Item>,
|
items: Vec<Item>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused, non_snake_case)]
|
#[allow(unused, non_snake_case)]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Item {
|
struct Item {
|
||||||
volumeInfo: VolumeInfo,
|
volumeInfo: VolumeInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused, non_snake_case)]
|
#[allow(unused, non_snake_case)]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct VolumeInfo {
|
struct VolumeInfo {
|
||||||
title: String,
|
title: String,
|
||||||
authors: Option<Vec<String>>,
|
authors: Option<Vec<String>>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
imageLinks: Option<ImageLinks>,
|
imageLinks: Option<ImageLinks>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused, non_snake_case)]
|
#[allow(unused, non_snake_case)]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct ImageLinks {
|
struct ImageLinks {
|
||||||
thumbnail: Option<String>,
|
thumbnail: Option<String>,
|
||||||
smallThumbnail: Option<String>,
|
smallThumbnail: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
fn main() -> eframe::Result<()> {
|
||||||
async fn main() {
|
env_logger::init();
|
||||||
let args: Vec<String> = env::args().collect();
|
let options = eframe::NativeOptions {
|
||||||
let path_to_scanner = &args[1];
|
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 880.0]),
|
||||||
let mut port = serial::open(path_to_scanner).unwrap();
|
..Default::default()
|
||||||
interact(&mut port).await.unwrap();
|
};
|
||||||
|
eframe::run_native(
|
||||||
|
"Barcode Scanner",
|
||||||
|
options,
|
||||||
|
Box::new(|cc| {
|
||||||
|
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||||
|
Ok(Box::<BarcodeScanner>::default())
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn interact<T: SerialPort>(port: &mut T) -> io::Result<()> {
|
fn get_ean13_data() -> String {
|
||||||
port.reconfigure(&|settings| {
|
let args: Vec<String> = env::args().collect();
|
||||||
settings.set_baud_rate(serial::Baud9600)?;
|
let path_to_scanner = &args[1];
|
||||||
settings.set_char_size(serial::Bits8);
|
let mut port = serial::open(path_to_scanner).unwrap();
|
||||||
settings.set_parity(serial::ParityNone);
|
interact(&mut port).to_string()
|
||||||
settings.set_stop_bits(serial::Stop1);
|
}
|
||||||
settings.set_flow_control(serial::FlowNone);
|
fn interact<T: SerialPort>(port: &mut T) -> String {
|
||||||
Ok(())
|
port.reconfigure(&|settings| {
|
||||||
})?;
|
settings.set_baud_rate(serial::Baud9600).unwrap();
|
||||||
|
settings.set_char_size(serial::Bits8);
|
||||||
port.set_timeout(Duration::from_millis(1000000000))?;
|
settings.set_parity(serial::ParityNone);
|
||||||
loop {
|
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];
|
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();
|
port.read_exact(buf).unwrap();
|
||||||
let barcode_id = from_utf8(buf).unwrap();
|
let barcode_id = from_utf8(buf).unwrap();
|
||||||
println!("{}", &barcode_id);
|
debug!("{}", &barcode_id);
|
||||||
if barcode_id.starts_with("978") {
|
barcode_id.to_string()
|
||||||
get_book_data(barcode_id.to_string()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_book_data(isbn: String) {
|
fn get_book_data(isbn: String) -> BookData {
|
||||||
let lookup_url = format!(
|
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||||
"https://www.googleapis.com/books/v1/volumes?q=isbn:{}",
|
let lookup_url = format!(
|
||||||
isbn
|
"https://www.googleapis.com/books/v1/volumes?q=isbn:{}",
|
||||||
);
|
isbn
|
||||||
let cleint = Client::new();
|
);
|
||||||
let book_data = cleint
|
let cleint = Client::new();
|
||||||
.get(lookup_url)
|
let book_data = runtime
|
||||||
.send()
|
.block_on(cleint.get(lookup_url).send())
|
||||||
.await
|
.unwrap()
|
||||||
.unwrap()
|
.json::<BookRoot>();
|
||||||
.json::<BookRoot>()
|
let book_data = runtime.block_on(book_data).unwrap();
|
||||||
.await
|
let book_metadata: Vec<BookData> = book_data.items.into_iter().map(BookData::from).collect();
|
||||||
.unwrap();
|
println!("{}", &book_metadata[0]);
|
||||||
let book_metadata: Vec<BookData> = book_data.items.into_iter().map(BookData::from).collect();
|
return book_metadata[0].clone();
|
||||||
println!("{}", &book_metadata[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Item> for BookData {
|
impl From<Item> for BookData {
|
||||||
fn from(item: Item) -> Self {
|
fn from(item: Item) -> Self {
|
||||||
let info = item.volumeInfo;
|
let info = item.volumeInfo;
|
||||||
BookData {
|
BookData {
|
||||||
title: info.title,
|
title: info.title,
|
||||||
authors: info.authors.unwrap_or_default(),
|
authors: info.authors.unwrap_or_default(),
|
||||||
description: info.description.as_deref().unwrap_or("").to_string(),
|
description: info.description.as_deref().unwrap_or("").to_string(),
|
||||||
thumbnail: info
|
thumbnail: info
|
||||||
.imageLinks
|
.imageLinks
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|links| links.thumbnail.as_deref())
|
.and_then(|links| links.thumbnail.as_deref())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string(),
|
.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 {
|
impl std::fmt::Display for BookData {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Title: {}\nAuthors: {}\nDescription: {}\nThumbnail: {}",
|
"Title: {}\nAuthors: {}\nDescription: {}\nThumbnail: {}",
|
||||||
self.title,
|
self.title,
|
||||||
if self.authors.is_empty() {
|
if self.authors.is_empty() {
|
||||||
"Unknown".to_string()
|
"Unknown".to_string()
|
||||||
} else {
|
} else {
|
||||||
self.authors.join(", ")
|
self.authors.join(", ")
|
||||||
},
|
},
|
||||||
if self.description.is_empty() {
|
if self.description.is_empty() {
|
||||||
"No description available".to_string()
|
"No description available".to_string()
|
||||||
} else {
|
} else {
|
||||||
self.description.clone()
|
self.description.clone()
|
||||||
},
|
},
|
||||||
self.thumbnail
|
self.thumbnail
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
fn get_thumbnail(url: String, isbn: String) {
|
||||||
|
let runtime = Runtime::new().unwrap();
|
||||||
|
let url = if url.is_empty() {
|
||||||
|
"https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%2Fid%2FOIP.pluN_8e5GuVyqRmW8xWs1gHaET%3Fcb%3D12%26pid%3DApi&f=1&ipt=bf48e49e5df83d58cdee7752f4e2095f5b0ad37e5122d447ff774631db3de4a9&ipo=images".to_string()
|
||||||
|
} else {
|
||||||
|
url
|
||||||
|
};
|
||||||
|
debug!("The isbn of the img is: {isbn}");
|
||||||
|
let isbn = isbn.trim().to_string();
|
||||||
|
fs::create_dir_all("cache").unwrap();
|
||||||
|
let mut dest = File::create(format!("cache/{isbn}.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();
|
||||||
|
image::convert_to_webp(isbn);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BarcodeScanner {
|
||||||
|
barcode_rx: Receiver<String>,
|
||||||
|
last_barcode: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for BarcodeScanner {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) -> () {
|
||||||
|
egui_extras::install_image_loaders(ctx);
|
||||||
|
if let Ok(code) = self.barcode_rx.try_recv() {
|
||||||
|
self.last_barcode = Some(code);
|
||||||
|
}
|
||||||
|
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.clone())
|
||||||
|
} else {
|
||||||
|
BookData::new()
|
||||||
|
};
|
||||||
|
get_thumbnail(book_metadata.thumbnail.clone(), barcode);
|
||||||
|
ui.label(format!("Title: {}", book_metadata.title));
|
||||||
|
ui.label(format!("Description: {}", book_metadata.description));
|
||||||
|
ui.label(format!("Authors: {:?}", book_metadata.authors));
|
||||||
|
ui.image(egui::include_image!("cache/img_name.webp"))
|
||||||
|
.on_hover_text_at_pointer("WebP");
|
||||||
|
//}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
ctx.request_repaint_after(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for BarcodeScanner {
|
||||||
|
fn default() -> Self {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let mut port = serial::open("/dev/ttyACM0").unwrap();
|
||||||
|
port.reconfigure(&|settings| {
|
||||||
|
settings.set_baud_rate(serial::Baud9600)?;
|
||||||
|
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(1000)).unwrap();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut buf = [0u8; 14];
|
||||||
|
if let Ok(_) = port.read_exact(&mut buf) {
|
||||||
|
if let Ok(code) = std::str::from_utf8(&buf) {
|
||||||
|
let barcode = code.trim().to_string();
|
||||||
|
if !barcode.is_empty() {
|
||||||
|
let _ = tx.send(barcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
barcode_rx: rx,
|
||||||
|
last_barcode: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue