From 62c60723a77e9d3b984f94924194b1a0ba753916 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Sun, 5 Dec 2021 21:31:26 -0700 Subject: recognize connection errors too --- src/lib.rs | 190 +++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 127 insertions(+), 63 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index b0c7353..572b533 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use image::{DynamicImage, ImageFormat}; +use image::{DynamicImage, GrayImage, ImageFormat}; use std::cell::{Cell, RefCell}; use std::rc::Rc; @@ -40,6 +40,14 @@ fn get_queue_size(image: DynamicImage, corner: &(u32, u32)) -> DynamicImage { useful_region } +fn is_connection_error(image: &GrayImage) -> bool { + // prior experiments show that the 80th percentile is like 20 and the 100th percentile is 51 for the error state + let pct_80 = imageproc::stats::percentile(image, 80); + let pct_100 = imageproc::stats::percentile(image, 100); + + pct_80 < 25 && pct_100 < 60 +} + fn update_target_marker( target_marker: &HtmlElement, width: &Rc>, @@ -96,6 +104,120 @@ impl QueuePosition { } } +enum Status { + ETAPending, + HasETA(js_sys::Date), + ETAFucked, + ConnectionError, +} + +impl Status { + fn render_title(&self) -> String { + match self { + Status::ETAPending => format!("queue go brrr - ETA pending"), + Status::ETAFucked => format!("queue go brrr - ETA fucked (try reloading i guess?)"), + Status::HasETA(date) => { + let empty_array = js_sys::Array::new(); + let options = { + let kv_pair = js_sys::Array::new(); + kv_pair.push(&JsValue::from("timeStyle")); + kv_pair.push(&JsValue::from("short")); + let kv_pairs = js_sys::Array::new(); + kv_pairs.push(&kv_pair); + js_sys::Object::from_entries(&kv_pairs).unwrap() + }; + let format = js_sys::Intl::DateTimeFormat::new(&empty_array, &options); + let format = format.format(); + let locale_string = format.call1(&JsValue::UNDEFINED, &date).unwrap(); + locale_string.as_string().unwrap() + } + Status::ConnectionError => format!("queue go brrr - connection error?"), + } + } +} + +#[derive(Clone)] +struct State { + history: Rc>, + status: Rc>, + queue_position: QueuePosition, + preview_image: Element, + current_queue_position: HtmlElement, + title_h1: HtmlElement, + title_element: HtmlElement, +} + +impl State { + fn new(queue_position: &QueuePosition) -> Self { + let document = gloo::utils::document(); + let preview_image = document.get_element_by_id("photo").unwrap(); + let current_queue_position = document + .get_element_by_id("current-queue-position") + .unwrap() + .dyn_into() + .unwrap(); + let title_h1 = document + .get_element_by_id("title") + .unwrap() + .dyn_into() + .unwrap(); + let title_element = document + .query_selector("title") + .unwrap() + .unwrap() + .dyn_into() + .unwrap(); + Self { + history: Rc::new(RefCell::new(History::default())), + status: Rc::new(RefCell::new(Status::ETAPending)), + queue_position: queue_position.clone(), + preview_image, + current_queue_position, + title_h1, + title_element, + } + } + + async fn update(&self, data: Blob) { + let image = blob_to_image(data).await; + let useful_region = get_queue_size(image, &self.queue_position.get()); + let data_uri = image_to_data_uri(&useful_region); + self.preview_image.set_attribute("src", &data_uri).unwrap(); + let useful_region = image::imageops::grayscale(&useful_region); + let new_status = if is_connection_error(&useful_region) { + Status::ConnectionError + } else { + let queue_size = ocr::ocr(&useful_region); + match queue_size { + Some(queue_size) => { + self.current_queue_position + .set_inner_text(&format!("{}", queue_size)); + match self.history.try_borrow_mut() { + Ok(mut history) => { + history.record(queue_size); + let estimated_finish = history.completion_time(); + match estimated_finish { + Ok(finish) => Status::HasETA(finish), + Err(NoETA::Pending) => Status::ETAPending, + Err(NoETA::Fuck) => Status::ETAFucked, + } + } + Err(_) => return, + } + } + None => { + self.current_queue_position + .set_inner_text("something i can't read"); + return; + } + } + }; + let title = new_status.render_title(); + self.title_h1.set_inner_text(&title); + self.title_element.set_inner_text(&title); + } +} + async fn do_boot() { let width = Rc::new(Cell::new(0)); let height = Rc::new(Cell::new(0)); @@ -106,7 +228,7 @@ async fn do_boot() { let document = gloo::utils::document(); - let history = Rc::new(RefCell::new(History::default())); + let state = State::new(&queue_position); let (manual_update_tx, manual_update_rx) = mpsc::channel::<()>(5); @@ -198,8 +320,7 @@ async fn do_boot() { manual_update_rx, ); let update_future = update_stream.for_each(move |_| { - let history = history.clone(); - let queue_position = queue_position.clone(); + let state = state.clone(); if !streaming.get() { return futures::future::ready(()); } @@ -220,66 +341,9 @@ async fn do_boot() { canvas .to_blob( Closure::once_into_js(move |data: Blob| { - let history = history.clone(); - let queue_position = queue_position.clone(); - // lifetime bullshit here - let document = gloo::utils::document(); - let photo = document.get_element_by_id("photo").unwrap(); - let current_queue_position: HtmlElement = document - .get_element_by_id("current-queue-position") - .unwrap() - .dyn_into() - .unwrap(); - let eta: HtmlElement = document - .get_element_by_id("eta") - .unwrap() - .dyn_into() - .unwrap(); + let state = state.clone(); spawn_local(async move { - let image = blob_to_image(data).await; - let useful_region = get_queue_size(image, &queue_position.get()); - let data_uri = image_to_data_uri(&useful_region); - photo.set_attribute("src", &data_uri).unwrap(); - let queue_size = ocr::ocr(useful_region); - match queue_size { - Some(queue_size) => { - current_queue_position.set_inner_text(&format!("{}", queue_size)); - if let Ok(mut history) = history.try_borrow_mut() { - history.record(queue_size); - let estimated_finish = history.completion_time(); - let eta_text = match estimated_finish { - Ok(finish) => { - let empty_array = js_sys::Array::new(); - let options = { - let kv_pair = js_sys::Array::new(); - kv_pair.push(&JsValue::from("timeStyle")); - kv_pair.push(&JsValue::from("short")); - let kv_pairs = js_sys::Array::new(); - kv_pairs.push(&kv_pair); - js_sys::Object::from_entries(&kv_pairs).unwrap() - }; - let format = js_sys::Intl::DateTimeFormat::new( - &empty_array, - &options, - ); - let format = format.format(); - let locale_string = - format.call1(&JsValue::UNDEFINED, &finish).unwrap(); - locale_string.as_string().unwrap() - } - Err(NoETA::Pending) => format!("pending"), - Err(NoETA::Fuck) => { - format!("fucked (try reloading i guess?)") - } - }; - let label = format!(" - ETA {}", eta_text); - eta.set_inner_text(&label); - } - } - None => { - current_queue_position.set_inner_text("something i can't read"); - } - } + state.update(data).await; }); }) .dyn_ref() -- cgit v1.2.3