Update our local cache in the StateMachine

This commit is contained in:
Jeremy Wall 2023-01-02 12:54:10 -06:00
parent c424432def
commit 0f28b758fa
3 changed files with 98 additions and 101 deletions

View File

@ -188,6 +188,13 @@ impl LocalStore {
Some(recipe_list) Some(recipe_list)
} }
pub fn get_recipe_entry(&self, id: &str) -> Option<RecipeEntry> {
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 /// Sets the set of recipes to the entries passed in. Deletes any recipes not
/// in the list. /// in the list.
pub fn set_all_recipes(&self, entries: &Vec<RecipeEntry>) { pub fn set_all_recipes(&self, entries: &Vec<RecipeEntry>) {
@ -314,7 +321,6 @@ impl HttpStore {
.await .await
.expect("Unparseable authentication response") .expect("Unparseable authentication response")
.as_success(); .as_success();
self.local_store.set_user_data(user_data.as_ref());
return user_data; return user_data;
} }
error!(status = resp.status(), "Login was unsuccessful") error!(status = resp.status(), "Login was unsuccessful")
@ -340,14 +346,12 @@ impl HttpStore {
}; };
if resp.status() == 404 { if resp.status() == 404 {
debug!("Categories returned 404"); debug!("Categories returned 404");
self.local_store.set_categories(None);
Ok(None) Ok(None)
} else if resp.status() != 200 { } else if resp.status() != 200 {
Err(format!("Status: {}", resp.status()).into()) Err(format!("Status: {}", resp.status()).into())
} else { } else {
debug!("We got a valid response back!"); debug!("We got a valid response back!");
let resp = resp.json::<CategoryResponse>().await?.as_success().unwrap(); let resp = resp.json::<CategoryResponse>().await?.as_success().unwrap();
self.local_store.set_categories(Some(&resp));
Ok(Some(resp)) Ok(Some(resp))
} }
} }
@ -375,9 +379,6 @@ impl HttpStore {
.await .await
.map_err(|e| format!("{}", e))? .map_err(|e| format!("{}", e))?
.as_success(); .as_success();
if let Some(ref entries) = entries {
self.local_store.set_all_recipes(&entries);
}
Ok(entries) Ok(entries)
} }
} }
@ -389,15 +390,11 @@ impl HttpStore {
let mut path = self.v1_path(); let mut path = self.v1_path();
path.push_str("/recipe/"); path.push_str("/recipe/");
path.push_str(id.as_ref()); path.push_str(id.as_ref());
let storage = js_lib::get_storage();
let resp = match reqwasm::http::Request::get(&path).send().await { let resp = match reqwasm::http::Request::get(&path).send().await {
Ok(resp) => resp, Ok(resp) => resp,
Err(reqwasm::Error::JsError(err)) => { Err(reqwasm::Error::JsError(err)) => {
error!(path, ?err, "Error hitting api"); error!(path, ?err, "Error hitting api");
return match storage.get(&recipe_key(&id))? { return Ok(self.local_store.get_recipe_entry(id.as_ref()));
Some(s) => Ok(Some(from_str(&s).map_err(|e| format!("{}", e))?)),
None => Ok(None),
};
} }
Err(err) => { Err(err) => {
return Err(err)?; return Err(err)?;
@ -417,8 +414,7 @@ impl HttpStore {
.as_success() .as_success()
.unwrap(); .unwrap();
if let Some(ref entry) = entry { if let Some(ref entry) = entry {
let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?; self.local_store.set_recipe_entry(entry);
storage.set(&recipe_key(entry.recipe_id()), &serialized)?
} }
Ok(entry) Ok(entry)
} }
@ -432,7 +428,6 @@ impl HttpStore {
if r.recipe_id().is_empty() { if r.recipe_id().is_empty() {
return Err("Recipe Ids can not be empty".into()); 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 serialized = to_string(&recipes).expect("Unable to serialize recipe entries");
let resp = reqwasm::http::Request::post(&path) let resp = reqwasm::http::Request::post(&path)
@ -452,7 +447,6 @@ impl HttpStore {
pub async fn save_categories(&self, categories: String) -> Result<(), Error> { pub async fn save_categories(&self, categories: String) -> Result<(), Error> {
let mut path = self.v1_path(); let mut path = self.v1_path();
path.push_str("/categories"); path.push_str("/categories");
self.local_store.set_categories(Some(&categories));
let resp = reqwasm::http::Request::post(&path) let resp = reqwasm::http::Request::post(&path)
.body(to_string(&categories).expect("Unable to encode categories as json")) .body(to_string(&categories).expect("Unable to encode categories as json"))
.header("content-type", "application/json") .header("content-type", "application/json")
@ -490,7 +484,6 @@ impl HttpStore {
pub async fn save_plan(&self, plan: Vec<(String, i32)>) -> Result<(), Error> { pub async fn save_plan(&self, plan: Vec<(String, i32)>) -> Result<(), Error> {
let mut path = self.v1_path(); let mut path = self.v1_path();
path.push_str("/plan"); path.push_str("/plan");
self.local_store.save_plan(&plan);
let resp = reqwasm::http::Request::post(&path) let resp = reqwasm::http::Request::post(&path)
.body(to_string(&plan).expect("Unable to encode plan as json")) .body(to_string(&plan).expect("Unable to encode plan as json"))
.header("content-type", "application/json") .header("content-type", "application/json")
@ -517,9 +510,6 @@ impl HttpStore {
.await .await
.map_err(|e| format!("{}", e))? .map_err(|e| format!("{}", e))?
.as_success(); .as_success();
if let Some(ref entry) = plan {
self.local_store.save_plan(&entry);
}
Ok(plan) Ok(plan)
} }
} }
@ -536,7 +526,6 @@ impl HttpStore {
> { > {
let mut path = self.v2_path(); let mut path = self.v2_path();
path.push_str("/inventory"); path.push_str("/inventory");
let storage = js_lib::get_storage();
let resp = reqwasm::http::Request::get(&path).send().await?; let resp = reqwasm::http::Request::get(&path).send().await?;
if resp.status() != 200 { if resp.status() != 200 {
let err = Err(format!("Status: {}", resp.status()).into()); let err = Err(format!("Status: {}", resp.status()).into());
@ -556,11 +545,6 @@ impl HttpStore {
.map_err(|e| format!("{}", e))? .map_err(|e| format!("{}", e))?
.as_success() .as_success()
.unwrap(); .unwrap();
self.local_store.set_inventory_data((
&(filtered_ingredients.iter().cloned().collect()),
&(modified_amts.iter().cloned().collect()),
&extra_items,
));
Ok(( Ok((
filtered_ingredients.into_iter().collect(), filtered_ingredients.into_iter().collect(),
modified_amts.into_iter().collect(), modified_amts.into_iter().collect(),
@ -581,11 +565,6 @@ impl HttpStore {
let filtered_ingredients: Vec<IngredientKey> = filtered_ingredients.into_iter().collect(); let filtered_ingredients: Vec<IngredientKey> = filtered_ingredients.into_iter().collect();
let modified_amts: Vec<(IngredientKey, String)> = modified_amts.into_iter().collect(); let modified_amts: Vec<(IngredientKey, String)> = modified_amts.into_iter().collect();
debug!("Storing inventory data in cache"); 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)) let serialized_inventory = to_string(&(filtered_ingredients, modified_amts, extra_items))
.expect("Unable to encode plan as json"); .expect("Unable to encode plan as json");
debug!("Storing inventory data via API"); debug!("Storing inventory data via API");

View File

@ -15,15 +15,13 @@ use std::collections::{BTreeMap, BTreeSet};
use client_api::UserData; use client_api::UserData;
use recipes::{parse, IngredientKey, Recipe, RecipeEntry}; use recipes::{parse, IngredientKey, Recipe, RecipeEntry};
use serde_json::from_str;
use sycamore::futures::spawn_local_scoped; use sycamore::futures::spawn_local_scoped;
use sycamore::prelude::*; use sycamore::prelude::*;
use sycamore_state::{Handler, MessageMapper}; use sycamore_state::{Handler, MessageMapper};
use tracing::{debug, error, info, instrument, warn}; use tracing::{debug, error, info, instrument, warn};
use wasm_bindgen::throw_str; use wasm_bindgen::{throw_str, UnwrapThrowExt};
use crate::api::HttpStore; use crate::api::{HttpStore, LocalStore};
use crate::js_lib;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct AppState { pub struct AppState {
@ -61,25 +59,19 @@ pub enum Message {
UpdateExtra(usize, String, String), UpdateExtra(usize, String, String),
SaveRecipe(RecipeEntry), SaveRecipe(RecipeEntry),
SetRecipe(String, Recipe), 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<Recipe>),
SetCategoryMap(String), SetCategoryMap(String),
ResetInventory, ResetInventory,
AddFilteredIngredient(IngredientKey), AddFilteredIngredient(IngredientKey),
UpdateAmt(IngredientKey, String), UpdateAmt(IngredientKey, String),
SetUserData(UserData), SetUserData(UserData),
// TODO(jwall): Remove this annotation when safe to do so.
#[allow(dead_code)]
UnsetUserData,
SaveState, SaveState,
LoadState, LoadState,
} }
pub struct StateMachine(HttpStore); pub struct StateMachine {
store: HttpStore,
local_store: LocalStore,
}
#[instrument] #[instrument]
fn filter_recipes( fn filter_recipes(
@ -110,27 +102,26 @@ fn filter_recipes(
} }
impl StateMachine { impl StateMachine {
async fn load_state(store: HttpStore, original: &Signal<AppState>) { pub fn new(store: HttpStore, local_store: LocalStore) -> Self {
Self { store, local_store }
}
async fn load_state(
store: &HttpStore,
local_store: &LocalStore,
original: &Signal<AppState>,
) -> Result<(), crate::api::Error> {
let mut state = original.get().as_ref().clone(); let mut state = original.get().as_ref().clone();
info!("Synchronizing Recipes"); info!("Synchronizing Recipes");
// TODO(jwall): Make our caching logic using storage more robust. let recipe_entries = &store.get_recipes().await?;
let recipe_entries = match store.get_recipes().await { let (staples, recipes) = filter_recipes(&recipe_entries)?;
Ok(recipe_entries) => { if let Some(recipes) = recipes {
if let Ok((staples, recipes)) = filter_recipes(&recipe_entries) { state.staples = staples;
state.staples = staples; state.recipes = recipes;
if let Some(recipes) = recipes {
state.recipes = recipes;
}
}
recipe_entries
}
Err(err) => {
error!(?err);
None
}
}; };
if let Ok(Some(plan)) = store.get_plan().await { let plan = store.get_plan().await?;
if let Some(plan) = plan {
// set the counts. // set the counts.
let mut plan_map = BTreeMap::new(); let mut plan_map = BTreeMap::new();
for (id, count) in plan { for (id, count) in plan {
@ -146,15 +137,8 @@ impl StateMachine {
} }
} }
info!("Checking for user_data in local storage"); info!("Checking for user_data in local storage");
let storage = js_lib::get_storage(); let user_data = local_store.get_user_data();
let user_data = storage state.auth = user_data;
.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);
}
}
info!("Synchronizing categories"); info!("Synchronizing categories");
match store.get_categories().await { match store.get_categories().await {
Ok(Some(categories_content)) => { Ok(Some(categories_content)) => {
@ -180,6 +164,7 @@ impl StateMachine {
} }
} }
original.set(state); original.set(state);
Ok(())
} }
} }
@ -194,28 +179,51 @@ impl MessageMapper<Message, AppState> for StateMachine {
for (id, _) in original_copy.recipes.iter() { for (id, _) in original_copy.recipes.iter() {
map.insert(id.clone(), 0); 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; original_copy.recipe_counts = map;
} }
Message::UpdateRecipeCount(id, count) => { Message::UpdateRecipeCount(id, count) => {
original_copy.recipe_counts.insert(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) => { Message::AddExtra(amt, name) => {
original_copy.extras.push((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) => { Message::RemoveExtra(idx) => {
original_copy.extras.remove(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) { Message::UpdateExtra(idx, amt, name) => {
Some(extra) => { match original_copy.extras.get_mut(idx) {
extra.0 = amt; Some(extra) => {
extra.1 = name; extra.0 = amt;
extra.1 = name;
}
None => {
throw_str("Attempted to remove extra that didn't exist");
}
} }
None => { self.local_store.set_inventory_data((
throw_str("Attempted to remove extra that didn't exist"); &original_copy.filtered_ingredients,
} &original_copy.modified_amts,
}, &original_copy.extras,
Message::SetStaples(staples) => { ))
original_copy.staples = staples;
} }
Message::SetRecipe(id, recipe) => { Message::SetRecipe(id, recipe) => {
original_copy.recipes.insert(id, recipe); original_copy.recipes.insert(id, recipe);
@ -226,22 +234,21 @@ impl MessageMapper<Message, AppState> for StateMachine {
original_copy original_copy
.recipes .recipes
.insert(entry.recipe_id().to_owned(), recipe); .insert(entry.recipe_id().to_owned(), recipe);
let store = self.0.clone();
original_copy original_copy
.recipe_counts .recipe_counts
.insert(entry.recipe_id().to_owned(), 0); .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 { spawn_local_scoped(cx, async move {
if let Err(e) = store.save_recipes(vec![entry]).await { if let Err(e) = store.save_recipes(vec![entry]).await {
error!(err=?e, "Unable to save Recipe"); error!(err=?e, "Unable to save Recipe");
} }
}); });
} }
Message::RemoveRecipe(id) => {
original_copy.recipes.remove(&id);
}
Message::SetCategoryMap(category_text) => { Message::SetCategoryMap(category_text) => {
let store = self.0.clone();
original_copy.category_map = category_text.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 { spawn_local_scoped(cx, async move {
if let Err(e) = store.save_categories(category_text).await { if let Err(e) = store.save_categories(category_text).await {
error!(?e, "Failed to save categories"); error!(?e, "Failed to save categories");
@ -252,22 +259,34 @@ impl MessageMapper<Message, AppState> for StateMachine {
original_copy.filtered_ingredients = BTreeSet::new(); original_copy.filtered_ingredients = BTreeSet::new();
original_copy.modified_amts = BTreeMap::new(); original_copy.modified_amts = BTreeMap::new();
original_copy.extras = Vec::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) => { Message::AddFilteredIngredient(key) => {
original_copy.filtered_ingredients.insert(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) => { Message::UpdateAmt(key, amt) => {
original_copy.modified_amts.insert(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) => { Message::SetUserData(user_data) => {
original_copy.auth = Some(user_data); original_copy.auth = Some(user_data);
} }
Message::UnsetUserData => {
original_copy.auth = None;
}
Message::SaveState => { Message::SaveState => {
let store = self.0.clone();
let original_copy = original_copy.clone(); let original_copy = original_copy.clone();
let store = self.store.clone();
spawn_local_scoped(cx, async move { spawn_local_scoped(cx, async move {
if let Err(e) = store.save_app_state(original_copy).await { if let Err(e) = store.save_app_state(original_copy).await {
error!(err=?e, "Error saving app state") error!(err=?e, "Error saving app state")
@ -275,9 +294,17 @@ impl MessageMapper<Message, AppState> for StateMachine {
}); });
} }
Message::LoadState => { 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 { 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; return;
} }
@ -293,5 +320,5 @@ pub fn get_state_handler<'ctx>(
initial: AppState, initial: AppState,
store: HttpStore, store: HttpStore,
) -> StateHandler<'ctx> { ) -> StateHandler<'ctx> {
Handler::new(cx, initial, StateMachine(store)) Handler::new(cx, initial, StateMachine::new(store, LocalStore::new()))
} }

View File

@ -43,12 +43,3 @@ pub fn get_storage() -> Storage {
.expect("Failed to get storage") .expect("Failed to get storage")
.expect("No storage available") .expect("No storage available")
} }
pub fn get_storage_keys() -> Vec<String> {
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
}