mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Update our local cache in the StateMachine
This commit is contained in:
parent
c424432def
commit
0f28b758fa
@ -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");
|
||||
|
@ -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()))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user