From 0f28b758fa5345f35ea6fec09d3a7b3ba4618bb4 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Mon, 2 Jan 2023 12:54:10 -0600 Subject: [PATCH] Update our local cache in the StateMachine --- web/src/api.rs | 39 +++-------- web/src/app_state.rs | 151 +++++++++++++++++++++++++------------------ web/src/js_lib.rs | 9 --- 3 files changed, 98 insertions(+), 101 deletions(-) diff --git a/web/src/api.rs b/web/src/api.rs index 40e1519..72d35b1 100644 --- a/web/src/api.rs +++ b/web/src/api.rs @@ -188,6 +188,13 @@ impl LocalStore { Some(recipe_list) } + pub fn get_recipe_entry(&self, id: &str) -> Option { + self.store + .get(&recipe_key(id)) + .unwrap_throw() + .map(|entry| from_str(&entry).unwrap_throw()) + } + /// Sets the set of recipes to the entries passed in. Deletes any recipes not /// in the list. pub fn set_all_recipes(&self, entries: &Vec) { @@ -314,7 +321,6 @@ impl HttpStore { .await .expect("Unparseable authentication response") .as_success(); - self.local_store.set_user_data(user_data.as_ref()); return user_data; } error!(status = resp.status(), "Login was unsuccessful") @@ -340,14 +346,12 @@ impl HttpStore { }; if resp.status() == 404 { debug!("Categories returned 404"); - self.local_store.set_categories(None); Ok(None) } else if resp.status() != 200 { Err(format!("Status: {}", resp.status()).into()) } else { debug!("We got a valid response back!"); let resp = resp.json::().await?.as_success().unwrap(); - self.local_store.set_categories(Some(&resp)); Ok(Some(resp)) } } @@ -375,9 +379,6 @@ impl HttpStore { .await .map_err(|e| format!("{}", e))? .as_success(); - if let Some(ref entries) = entries { - self.local_store.set_all_recipes(&entries); - } Ok(entries) } } @@ -389,15 +390,11 @@ impl HttpStore { let mut path = self.v1_path(); path.push_str("/recipe/"); path.push_str(id.as_ref()); - let storage = js_lib::get_storage(); let resp = match reqwasm::http::Request::get(&path).send().await { Ok(resp) => resp, Err(reqwasm::Error::JsError(err)) => { error!(path, ?err, "Error hitting api"); - return match storage.get(&recipe_key(&id))? { - Some(s) => Ok(Some(from_str(&s).map_err(|e| format!("{}", e))?)), - None => Ok(None), - }; + return Ok(self.local_store.get_recipe_entry(id.as_ref())); } Err(err) => { return Err(err)?; @@ -417,8 +414,7 @@ impl HttpStore { .as_success() .unwrap(); if let Some(ref entry) = entry { - let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?; - storage.set(&recipe_key(entry.recipe_id()), &serialized)? + self.local_store.set_recipe_entry(entry); } Ok(entry) } @@ -432,7 +428,6 @@ impl HttpStore { if r.recipe_id().is_empty() { return Err("Recipe Ids can not be empty".into()); } - self.local_store.set_recipe_entry(&r); } let serialized = to_string(&recipes).expect("Unable to serialize recipe entries"); let resp = reqwasm::http::Request::post(&path) @@ -452,7 +447,6 @@ impl HttpStore { pub async fn save_categories(&self, categories: String) -> Result<(), Error> { let mut path = self.v1_path(); path.push_str("/categories"); - self.local_store.set_categories(Some(&categories)); let resp = reqwasm::http::Request::post(&path) .body(to_string(&categories).expect("Unable to encode categories as json")) .header("content-type", "application/json") @@ -490,7 +484,6 @@ impl HttpStore { pub async fn save_plan(&self, plan: Vec<(String, i32)>) -> Result<(), Error> { let mut path = self.v1_path(); path.push_str("/plan"); - self.local_store.save_plan(&plan); let resp = reqwasm::http::Request::post(&path) .body(to_string(&plan).expect("Unable to encode plan as json")) .header("content-type", "application/json") @@ -517,9 +510,6 @@ impl HttpStore { .await .map_err(|e| format!("{}", e))? .as_success(); - if let Some(ref entry) = plan { - self.local_store.save_plan(&entry); - } Ok(plan) } } @@ -536,7 +526,6 @@ impl HttpStore { > { let mut path = self.v2_path(); path.push_str("/inventory"); - let storage = js_lib::get_storage(); let resp = reqwasm::http::Request::get(&path).send().await?; if resp.status() != 200 { let err = Err(format!("Status: {}", resp.status()).into()); @@ -556,11 +545,6 @@ impl HttpStore { .map_err(|e| format!("{}", e))? .as_success() .unwrap(); - self.local_store.set_inventory_data(( - &(filtered_ingredients.iter().cloned().collect()), - &(modified_amts.iter().cloned().collect()), - &extra_items, - )); Ok(( filtered_ingredients.into_iter().collect(), modified_amts.into_iter().collect(), @@ -581,11 +565,6 @@ impl HttpStore { let filtered_ingredients: Vec = filtered_ingredients.into_iter().collect(); let modified_amts: Vec<(IngredientKey, String)> = modified_amts.into_iter().collect(); debug!("Storing inventory data in cache"); - self.local_store.set_inventory_data(( - &(filtered_ingredients.iter().cloned().collect()), - &(modified_amts.iter().cloned().collect()), - &extra_items, - )); let serialized_inventory = to_string(&(filtered_ingredients, modified_amts, extra_items)) .expect("Unable to encode plan as json"); debug!("Storing inventory data via API"); diff --git a/web/src/app_state.rs b/web/src/app_state.rs index 50d21d9..4c49da2 100644 --- a/web/src/app_state.rs +++ b/web/src/app_state.rs @@ -15,15 +15,13 @@ use std::collections::{BTreeMap, BTreeSet}; use client_api::UserData; use recipes::{parse, IngredientKey, Recipe, RecipeEntry}; -use serde_json::from_str; use sycamore::futures::spawn_local_scoped; use sycamore::prelude::*; use sycamore_state::{Handler, MessageMapper}; use tracing::{debug, error, info, instrument, warn}; -use wasm_bindgen::throw_str; +use wasm_bindgen::{throw_str, UnwrapThrowExt}; -use crate::api::HttpStore; -use crate::js_lib; +use crate::api::{HttpStore, LocalStore}; #[derive(Debug, Clone, PartialEq)] pub struct AppState { @@ -61,25 +59,19 @@ pub enum Message { UpdateExtra(usize, String, String), SaveRecipe(RecipeEntry), SetRecipe(String, Recipe), - // TODO(jwall): Remove this annotation when safe to do so. - #[allow(dead_code)] - RemoveRecipe(String), - // TODO(jwall): Remove this annotation when safe to do so. - #[allow(dead_code)] - SetStaples(Option), SetCategoryMap(String), ResetInventory, AddFilteredIngredient(IngredientKey), UpdateAmt(IngredientKey, String), SetUserData(UserData), - // TODO(jwall): Remove this annotation when safe to do so. - #[allow(dead_code)] - UnsetUserData, SaveState, LoadState, } -pub struct StateMachine(HttpStore); +pub struct StateMachine { + store: HttpStore, + local_store: LocalStore, +} #[instrument] fn filter_recipes( @@ -110,27 +102,26 @@ fn filter_recipes( } impl StateMachine { - async fn load_state(store: HttpStore, original: &Signal) { + pub fn new(store: HttpStore, local_store: LocalStore) -> Self { + Self { store, local_store } + } + + async fn load_state( + store: &HttpStore, + local_store: &LocalStore, + original: &Signal, + ) -> Result<(), crate::api::Error> { let mut state = original.get().as_ref().clone(); info!("Synchronizing Recipes"); - // TODO(jwall): Make our caching logic using storage more robust. - let recipe_entries = match store.get_recipes().await { - Ok(recipe_entries) => { - if let Ok((staples, recipes)) = filter_recipes(&recipe_entries) { - state.staples = staples; - if let Some(recipes) = recipes { - state.recipes = recipes; - } - } - recipe_entries - } - Err(err) => { - error!(?err); - None - } + let recipe_entries = &store.get_recipes().await?; + let (staples, recipes) = filter_recipes(&recipe_entries)?; + if let Some(recipes) = recipes { + state.staples = staples; + state.recipes = recipes; }; - if let Ok(Some(plan)) = store.get_plan().await { + let plan = store.get_plan().await?; + if let Some(plan) = plan { // set the counts. let mut plan_map = BTreeMap::new(); for (id, count) in plan { @@ -146,15 +137,8 @@ impl StateMachine { } } info!("Checking for user_data in local storage"); - let storage = js_lib::get_storage(); - let user_data = storage - .get("user_data") - .expect("Couldn't read from storage"); - if let Some(data) = user_data { - if let Ok(user_data) = from_str(&data) { - state.auth = Some(user_data); - } - } + let user_data = local_store.get_user_data(); + state.auth = user_data; info!("Synchronizing categories"); match store.get_categories().await { Ok(Some(categories_content)) => { @@ -180,6 +164,7 @@ impl StateMachine { } } original.set(state); + Ok(()) } } @@ -194,28 +179,51 @@ impl MessageMapper for StateMachine { for (id, _) in original_copy.recipes.iter() { map.insert(id.clone(), 0); } + let plan: Vec<(String, i32)> = + map.iter().map(|(s, i)| (s.clone(), *i as i32)).collect(); + self.local_store.save_plan(&plan); original_copy.recipe_counts = map; } Message::UpdateRecipeCount(id, count) => { original_copy.recipe_counts.insert(id, count); + let plan: Vec<(String, i32)> = original_copy + .recipe_counts + .iter() + .map(|(s, i)| (s.clone(), *i as i32)) + .collect(); + self.local_store.save_plan(&plan); } Message::AddExtra(amt, name) => { original_copy.extras.push((amt, name)); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )) } Message::RemoveExtra(idx) => { original_copy.extras.remove(idx); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )) } - Message::UpdateExtra(idx, amt, name) => match original_copy.extras.get_mut(idx) { - Some(extra) => { - extra.0 = amt; - extra.1 = name; + Message::UpdateExtra(idx, amt, name) => { + match original_copy.extras.get_mut(idx) { + Some(extra) => { + extra.0 = amt; + extra.1 = name; + } + None => { + throw_str("Attempted to remove extra that didn't exist"); + } } - None => { - throw_str("Attempted to remove extra that didn't exist"); - } - }, - Message::SetStaples(staples) => { - original_copy.staples = staples; + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )) } Message::SetRecipe(id, recipe) => { original_copy.recipes.insert(id, recipe); @@ -226,22 +234,21 @@ impl MessageMapper for StateMachine { original_copy .recipes .insert(entry.recipe_id().to_owned(), recipe); - let store = self.0.clone(); original_copy .recipe_counts .insert(entry.recipe_id().to_owned(), 0); + let store = self.store.clone(); + self.local_store.set_recipe_entry(&entry); spawn_local_scoped(cx, async move { if let Err(e) = store.save_recipes(vec![entry]).await { error!(err=?e, "Unable to save Recipe"); } }); } - Message::RemoveRecipe(id) => { - original_copy.recipes.remove(&id); - } Message::SetCategoryMap(category_text) => { - let store = self.0.clone(); original_copy.category_map = category_text.clone(); + self.local_store.set_categories(Some(&category_text)); + let store = self.store.clone(); spawn_local_scoped(cx, async move { if let Err(e) = store.save_categories(category_text).await { error!(?e, "Failed to save categories"); @@ -252,22 +259,34 @@ impl MessageMapper for StateMachine { original_copy.filtered_ingredients = BTreeSet::new(); original_copy.modified_amts = BTreeMap::new(); original_copy.extras = Vec::new(); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )); } Message::AddFilteredIngredient(key) => { original_copy.filtered_ingredients.insert(key); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )); } Message::UpdateAmt(key, amt) => { original_copy.modified_amts.insert(key, amt); + self.local_store.set_inventory_data(( + &original_copy.filtered_ingredients, + &original_copy.modified_amts, + &original_copy.extras, + )); } Message::SetUserData(user_data) => { original_copy.auth = Some(user_data); } - Message::UnsetUserData => { - original_copy.auth = None; - } Message::SaveState => { - let store = self.0.clone(); let original_copy = original_copy.clone(); + let store = self.store.clone(); spawn_local_scoped(cx, async move { if let Err(e) = store.save_app_state(original_copy).await { error!(err=?e, "Error saving app state") @@ -275,9 +294,17 @@ impl MessageMapper for StateMachine { }); } Message::LoadState => { - let store = self.0.clone(); + let store = self.store.clone(); + let local_store = self.local_store.clone(); spawn_local_scoped(cx, async move { - Self::load_state(store, original).await; + Self::load_state(&store, &local_store, original) + .await + .unwrap_throw(); + local_store.set_inventory_data(( + &original.get().filtered_ingredients, + &original.get().modified_amts, + &original.get().extras, + )); }); return; } @@ -293,5 +320,5 @@ pub fn get_state_handler<'ctx>( initial: AppState, store: HttpStore, ) -> StateHandler<'ctx> { - Handler::new(cx, initial, StateMachine(store)) + Handler::new(cx, initial, StateMachine::new(store, LocalStore::new())) } diff --git a/web/src/js_lib.rs b/web/src/js_lib.rs index c4f2b73..ef99c4d 100644 --- a/web/src/js_lib.rs +++ b/web/src/js_lib.rs @@ -43,12 +43,3 @@ pub fn get_storage() -> Storage { .expect("Failed to get storage") .expect("No storage available") } - -pub fn get_storage_keys() -> Vec { - let storage = get_storage(); - let mut keys = Vec::new(); - for idx in 0..storage.length().unwrap() { - keys.push(get_storage().key(idx).unwrap().unwrap()) - } - keys -}