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)
}
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
/// in the list.
pub fn set_all_recipes(&self, entries: &Vec<RecipeEntry>) {
@ -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::<CategoryResponse>().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<IngredientKey> = 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");

View File

@ -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<Recipe>),
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<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();
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<Message, AppState> 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<Message, AppState> 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<Message, AppState> 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<Message, AppState> 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()))
}

View File

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