handle bad image bytes from vendor

This commit is contained in:
creating2morrow 2023-06-10 00:01:31 -04:00
parent 9fc930fb5b
commit e245c1d160
4 changed files with 233 additions and 126 deletions

View file

@ -132,3 +132,33 @@ pub async fn get_vendor_products(
} }
} }
} }
/// Send the request to vendor a single product
pub async fn get_vendor_product(
contact: String,
jwp: String,
pid: String,
) -> Result<Product, Box<dyn Error>> {
let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?;
let client = reqwest::Client::builder().proxy(proxy).build();
match client?
.get(format!("http://{}/market/product/{}", contact, pid))
.header("proof", jwp)
.send()
.await
{
Ok(response) => {
let res = response.json::<Product>().await;
debug!("get vendor product response: {:?}", res);
match res {
Ok(r) => Ok(r),
_ => Ok(Default::default()),
}
}
Err(e) => {
error!("failed to fetch product due to: {:?}", e);
Ok(Default::default())
}
}
}

View file

@ -10,6 +10,8 @@ pub struct MarketApp {
find_vendor: String, find_vendor: String,
get_vendor_products_tx: Sender<Vec<models::Product>>, get_vendor_products_tx: Sender<Vec<models::Product>>,
get_vendor_products_rx: Receiver<Vec<models::Product>>, get_vendor_products_rx: Receiver<Vec<models::Product>>,
get_vendor_product_tx: Sender<models::Product>,
get_vendor_product_rx: Receiver<models::Product>,
is_ordering: bool, is_ordering: bool,
is_pinging: bool, is_pinging: bool,
is_product_image_set: bool, is_product_image_set: bool,
@ -22,6 +24,7 @@ pub struct MarketApp {
is_vendor_enabled: bool, is_vendor_enabled: bool,
is_window_shopping: bool, is_window_shopping: bool,
orders: Vec<models::Order>, orders: Vec<models::Order>,
product_from_vendor: models::Product,
product_image: egui_extras::RetainedImage, product_image: egui_extras::RetainedImage,
products: Vec<models::Product>, products: Vec<models::Product>,
product_update_pid: String, product_update_pid: String,
@ -48,12 +51,15 @@ impl Default for MarketApp {
let is_vendor_enabled = r == contact::NEVEKO_VENDOR_MODE_ON; let is_vendor_enabled = r == contact::NEVEKO_VENDOR_MODE_ON;
let (contact_info_tx, contact_info_rx) = std::sync::mpsc::channel(); let (contact_info_tx, contact_info_rx) = std::sync::mpsc::channel();
let (get_vendor_products_tx, get_vendor_products_rx) = std::sync::mpsc::channel(); let (get_vendor_products_tx, get_vendor_products_rx) = std::sync::mpsc::channel();
let (get_vendor_product_tx, get_vendor_product_rx) = std::sync::mpsc::channel();
MarketApp { MarketApp {
contact_info_rx, contact_info_rx,
contact_info_tx, contact_info_tx,
find_vendor: utils::empty_string(), find_vendor: utils::empty_string(),
get_vendor_products_rx, get_vendor_products_rx,
get_vendor_products_tx, get_vendor_products_tx,
get_vendor_product_rx,
get_vendor_product_tx,
is_ordering: false, is_ordering: false,
is_pinging: false, is_pinging: false,
is_product_image_set: false, is_product_image_set: false,
@ -66,6 +72,7 @@ impl Default for MarketApp {
is_vendor_enabled, is_vendor_enabled,
is_window_shopping: false, is_window_shopping: false,
orders: Vec::new(), orders: Vec::new(),
product_from_vendor: Default::default(),
product_image: egui_extras::RetainedImage::from_image_bytes( product_image: egui_extras::RetainedImage::from_image_bytes(
"qr.png", "qr.png",
&read_product_image, &read_product_image,
@ -101,6 +108,9 @@ impl eframe::App for MarketApp {
if let Ok(vendor_products) = self.get_vendor_products_rx.try_recv() { if let Ok(vendor_products) = self.get_vendor_products_rx.try_recv() {
self.products = vendor_products; self.products = vendor_products;
} }
if let Ok(vendor_product) = self.get_vendor_product_rx.try_recv() {
self.product_from_vendor = vendor_product;
}
// TODO(c2m): create order form // TODO(c2m): create order form
@ -293,6 +303,167 @@ impl eframe::App for MarketApp {
} }
}); });
// Products Management window
//-----------------------------------------------------------------------------------
let mut is_showing_products = self.is_showing_products;
egui::Window::new("Products")
.open(&mut is_showing_products)
.vscroll(true)
.show(&ctx, |ui| {
use egui_extras::{
Column,
TableBuilder,
};
let table = TableBuilder::new(ui)
.striped(true)
.resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::remainder())
.min_scrolled_height(0.0);
table
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("Name");
});
header.col(|ui| {
ui.strong("Description");
});
header.col(|ui| {
ui.strong("Price");
});
header.col(|ui| {
ui.strong("Quantity");
});
header.col(|ui| {
ui.strong("Image");
});
header.col(|ui| {
ui.strong("");
});
})
.body(|mut body| {
for p in &self.products {
let row_height = 20.0;
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label(format!("{}", p.name));
});
row.col(|ui| {
ui.label(format!("{}", p.description));
});
row.col(|ui| {
ui.label(format!("{}", p.price));
});
row.col(|ui| {
ui.label(format!("{}", p.qty));
});
row.col(|ui| {
if ui.button("View").clicked() {
if !self.is_product_image_set {
self.is_showing_product_image = true;
let file_path = format!(
"/home/{}/.neveko/{}.jpeg",
std::env::var("USER")
.unwrap_or(String::from("user")),
p.pid
);
// For the sake of brevity product list doesn't have
// image bytes, get them
let mut i_product = product::find(&p.pid);
// only pull image from vendor when we want to view
if self.is_window_shopping {
send_product_from_vendor_req(
self.get_vendor_product_tx.clone(),
ctx.clone(),
self.vendor_status.i2p.clone(),
self.vendor_status.jwp.clone(),
String::from(&p.pid),
);
let e_product: models::Product = models::Product {
pid: self.product_from_vendor.pid.clone(),
description: self
.product_from_vendor
.description
.clone(),
image: self
.product_from_vendor
.image
.iter()
.cloned()
.collect(),
in_stock: self.product_from_vendor.in_stock,
name: self.product_from_vendor.name.clone(),
price: self.product_from_vendor.price,
qty: self.product_from_vendor.qty,
};
i_product = e_product;
}
match std::fs::write(&file_path, &i_product.image) {
Ok(w) => w,
Err(_) => {
log::error!("failed to write product image")
}
};
self.is_product_image_set = true;
let contents =
std::fs::read(&file_path).unwrap_or(Vec::new());
if !i_product.image.is_empty() {
// this image should uwrap if vendor image bytes are
// bad
let default_img = std::fs::read("./assets/qr.png")
.unwrap_or(Vec::new());
let default_r_img =
egui_extras::RetainedImage::from_image_bytes(
"qr.png",
&default_img,
)
.unwrap();
self.product_image =
egui_extras::RetainedImage::from_image_bytes(
file_path, &contents,
)
.unwrap_or(default_r_img);
}
self.is_product_image_set = true;
ctx.request_repaint();
}
}
});
row.col(|ui| {
ui.style_mut().wrap = Some(false);
ui.horizontal(|ui| {
if !self.is_window_shopping {
if ui.button("Update").clicked() {
self.product_update_pid = p.pid.clone();
self.new_product_desc = p.description.clone();
self.new_product_name = p.name.clone();
self.new_product_price = format!("{}", p.price);
self.new_product_qty = format!("{}", p.qty);
self.is_showing_product_update = true;
}
} else {
if ui.button("Create Order").clicked() {
self.product_update_pid = p.pid.clone();
self.is_ordering = true;
}
}
});
});
});
}
});
if ui.button("Exit").clicked() {
self.is_showing_products = false;
}
});
// Vendor specific // Vendor specific
// Update Product window // Update Product window
@ -373,129 +544,6 @@ impl eframe::App for MarketApp {
} }
}); });
// Vendor Products Management window
//-----------------------------------------------------------------------------------
let mut is_showing_products = self.is_showing_products;
egui::Window::new("Products")
.open(&mut is_showing_products)
.vscroll(true)
.show(&ctx, |ui| {
use egui_extras::{
Column,
TableBuilder,
};
let table = TableBuilder::new(ui)
.striped(true)
.resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::remainder())
.min_scrolled_height(0.0);
table
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("Name");
});
header.col(|ui| {
ui.strong("Description");
});
header.col(|ui| {
ui.strong("Price");
});
header.col(|ui| {
ui.strong("Quantity");
});
header.col(|ui| {
ui.strong("Image");
});
header.col(|ui| {
ui.strong("");
});
})
.body(|mut body| {
for p in &self.products {
let row_height = 20.0;
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label(format!("{}", p.name));
});
row.col(|ui| {
ui.label(format!("{}", p.description));
});
row.col(|ui| {
ui.label(format!("{}", p.price));
});
row.col(|ui| {
ui.label(format!("{}", p.qty));
});
row.col(|ui| {
if ui.button("View").clicked() {
if !self.is_product_image_set {
self.is_showing_product_image = true;
let file_path = format!(
"/home/{}/.neveko/{}.jpeg",
std::env::var("USER")
.unwrap_or(String::from("user")),
p.pid
);
// For the sake of brevity product list doesn't have
// image bytes, get them
let i_product = product::find(&p.pid);
match std::fs::write(&file_path, &i_product.image) {
Ok(w) => w,
Err(_) => {
log::error!("failed to write product image")
}
};
self.is_product_image_set = true;
let contents =
std::fs::read(&file_path).unwrap_or(Vec::new());
if !i_product.image.is_empty() {
self.product_image =
egui_extras::RetainedImage::from_image_bytes(
file_path, &contents,
)
.unwrap();
}
self.is_product_image_set = true;
ctx.request_repaint();
}
}
});
row.col(|ui| {
ui.style_mut().wrap = Some(false);
ui.horizontal(|ui| {
if !self.is_window_shopping {
if ui.button("Update").clicked() {
self.product_update_pid = p.pid.clone();
self.new_product_desc = p.description.clone();
self.new_product_name = p.name.clone();
self.new_product_price = format!("{}", p.price);
self.new_product_qty = format!("{}", p.qty);
self.is_showing_product_update = true;
}
} else {
if ui.button("Create Order").clicked() {
self.product_update_pid = p.pid.clone();
self.is_ordering = true;
}
}
});
});
});
}
});
if ui.button("Exit").clicked() {
self.is_showing_products = false;
}
});
// TODO(c2m): Orders window // TODO(c2m): Orders window
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
let mut is_showing_orders = self.is_showing_orders; let mut is_showing_orders = self.is_showing_orders;
@ -672,7 +720,6 @@ fn _refresh_on_delete_product_req(_tx: Sender<bool>, _ctx: egui::Context) {
}); });
} }
//------------------------------------------------------------------------------
fn send_contact_info_req(tx: Sender<models::Contact>, ctx: egui::Context, contact: String) { fn send_contact_info_req(tx: Sender<models::Contact>, ctx: egui::Context, contact: String) {
log::debug!("async send_contact_info_req"); log::debug!("async send_contact_info_req");
tokio::spawn(async move { tokio::spawn(async move {
@ -702,9 +749,28 @@ fn send_products_from_vendor_req(
let result = product::get_vendor_products(contact, jwp).await; let result = product::get_vendor_products(contact, jwp).await;
if result.is_ok() { if result.is_ok() {
let products: Vec<models::Product> = result.unwrap(); let products: Vec<models::Product> = result.unwrap();
log::info!("retreived {:?} products", products); log::info!("retreived {:?} products", products.len());
let _ = tx.send(products); let _ = tx.send(products);
ctx.request_repaint(); ctx.request_repaint();
} }
}); });
} }
fn send_product_from_vendor_req(
tx: Sender<models::Product>,
ctx: egui::Context,
contact: String,
jwp: String,
pid: String,
) {
log::debug!("fetching product {} from vendor: {}", &pid, contact);
tokio::spawn(async move {
let result = product::get_vendor_product(contact, jwp, pid).await;
if result.is_ok() {
let product: models::Product = result.unwrap();
log::info!("retreived product {}", &product.pid);
let _ = tx.send(product);
ctx.request_repaint();
}
});
}

View file

@ -31,6 +31,10 @@ async fn rocket() -> _ {
.mount("/products", routes![controller::get_products]) .mount("/products", routes![controller::get_products])
.mount( .mount(
"/product", "/product",
routes![controller::create_product, controller::update_product], routes![
controller::get_product,
controller::create_product,
controller::update_product
],
) )
} }

View file

@ -78,6 +78,13 @@ pub async fn gen_jwp(proof: Json<proof::TxProof>) -> Custom<Json<reqres::Jwp>> {
// NEVEKO Market APIs // NEVEKO Market APIs
//----------------------------------------------- //-----------------------------------------------
/// Get a product by passing id
#[get("/<pid>")]
pub async fn get_product(pid: String, _jwp: proof::PaymentProof) -> Custom<Json<models::Product>> {
let m_product: models::Product = product::find(&pid);
Custom(Status::Ok, Json(m_product))
}
/// Get all products /// Get all products
/// ///
/// Protected: true /// Protected: true