mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-21 19:29:49 -04:00
feat: migrate user_data and app_state
from localstorage to indexeddb
This commit is contained in:
parent
ed44e929f4
commit
1f90cc2ef6
@ -17,7 +17,7 @@ use base64::{self, Engine};
|
||||
use chrono::NaiveDate;
|
||||
use gloo_net;
|
||||
// TODO(jwall): Remove this when we have gone a few migrations past.
|
||||
//use serde_json::{from_str, to_string};
|
||||
use serde_json::{from_str, to_string};
|
||||
use sycamore::prelude::*;
|
||||
use tracing::{debug, error, instrument};
|
||||
|
||||
@ -26,6 +26,7 @@ use client_api::*;
|
||||
use recipes::{IngredientKey, RecipeEntry};
|
||||
use serde_wasm_bindgen::{from_value, to_value};
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::Storage;
|
||||
// TODO(jwall): Remove this when we have gone a few migrations past.
|
||||
//use web_sys::Storage;
|
||||
|
||||
@ -90,21 +91,38 @@ fn token68(user: String, pass: String) -> String {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LocalStore {
|
||||
// FIXME(zaphar): Migration from local storage to indexed db
|
||||
//old_store: Storage,
|
||||
// TODO(zaphar): Remove this when it's safe to delete the migration
|
||||
old_store: Storage,
|
||||
store: DBFactory<'static>,
|
||||
}
|
||||
|
||||
const APP_STATE_KEY: &'static str = "app-state";
|
||||
const USER_DATA_KEY: &'static str = "user_data";
|
||||
|
||||
impl LocalStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
store: DBFactory::default(),
|
||||
//old_store: js_lib::get_storage(),
|
||||
old_store: js_lib::get_storage(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn migrate(&self) {
|
||||
// 1. migrate app-state from localstore to indexeddb
|
||||
if let Ok(Some(v)) = self.old_store.get("app_state") {
|
||||
if let Ok(Some(local_state)) = from_str::<Option<AppState> >(&v) {
|
||||
self.store_app_state(&local_state).await;
|
||||
}
|
||||
}
|
||||
// 2. migrate user-state from localstore to indexeddb
|
||||
if let Ok(Some(v)) = self.old_store.get(USER_DATA_KEY) {
|
||||
if let Ok(local_user_data) = from_str::<Option<UserData> >(&v) {
|
||||
self.set_user_data(local_user_data.as_ref()).await;
|
||||
}
|
||||
}
|
||||
// 3. Recipes?
|
||||
}
|
||||
|
||||
pub async fn store_app_state(&self, state: &AppState) {
|
||||
//self.migrate_local_store().await;
|
||||
let state = match to_value(state) {
|
||||
@ -131,10 +149,6 @@ impl LocalStore {
|
||||
})
|
||||
.await
|
||||
.expect("Failed to store app-state");
|
||||
// FIXME(zaphar): Migration from local storage to indexed db
|
||||
//self.store
|
||||
// .set("app_state", &state)
|
||||
// .expect("Failed to set our app state");
|
||||
}
|
||||
|
||||
pub async fn fetch_app_state(&self) -> Option<AppState> {
|
||||
@ -163,30 +177,13 @@ impl LocalStore {
|
||||
})
|
||||
.await
|
||||
.expect("Failed to fetch app-state")
|
||||
// FIXME(zaphar): Migration from local storage to indexed db
|
||||
//self.store.get("app_state").map_or(None, |val| {
|
||||
// val.map(|s| {
|
||||
// debug!("Found an app_state object");
|
||||
// let mut app_state: AppState =
|
||||
// from_str(&s).expect("Failed to deserialize app state");
|
||||
// let recipes = parse_recipes(&self.get_recipes()).expect("Failed to parse recipes");
|
||||
// if let Some(recipes) = recipes {
|
||||
// debug!("Populating recipes");
|
||||
// for (id, recipe) in recipes {
|
||||
// debug!(id, "Adding recipe from local storage");
|
||||
// app_state.recipes.insert(id, recipe);
|
||||
// }
|
||||
// }
|
||||
// app_state
|
||||
// })
|
||||
//})
|
||||
}
|
||||
|
||||
/// Gets user data from local storage.
|
||||
pub async fn get_user_data(&self) -> Option<UserData> {
|
||||
self.store
|
||||
.ro_transaction(&[js_lib::STATE_STORE_NAME], |trx| async move {
|
||||
let key = to_value("user_data").expect("Failed to serialize key");
|
||||
let key = to_value(USER_DATA_KEY).expect("Failed to serialize key");
|
||||
let object_store = trx
|
||||
.object_store(js_lib::STATE_STORE_NAME)
|
||||
.expect("Failed to get object store");
|
||||
@ -202,16 +199,11 @@ impl LocalStore {
|
||||
})
|
||||
.await
|
||||
.expect("Failed to fetch user_data")
|
||||
// FIXME(zaphar): Migration from local storage to indexed db
|
||||
//self.store
|
||||
// .get("user_data")
|
||||
// .map_or(None, |val| val.map(|val| from_str(&val).unwrap_or(None)))
|
||||
// .flatten()
|
||||
}
|
||||
|
||||
// Set's user data to local storage.
|
||||
pub async fn set_user_data(&self, data: Option<&UserData>) {
|
||||
let key = to_value("user_data").expect("Failed to serialize key");
|
||||
let key = to_value(USER_DATA_KEY).expect("Failed to serialize key");
|
||||
if let Some(data) = data {
|
||||
let data = data.clone();
|
||||
self.store
|
||||
@ -230,13 +222,6 @@ impl LocalStore {
|
||||
})
|
||||
.await
|
||||
.expect("Failed to set user_data");
|
||||
// FIXME(zaphar): Migration from local storage to indexed db
|
||||
//self.store
|
||||
// .set(
|
||||
// "user_data",
|
||||
// &to_string(data).expect("Failed to desrialize user_data"),
|
||||
// )
|
||||
// .expect("Failed to set user_data");
|
||||
} else {
|
||||
self.store
|
||||
.rw_transaction(&[js_lib::STATE_STORE_NAME], |trx| async move {
|
||||
@ -251,10 +236,6 @@ impl LocalStore {
|
||||
})
|
||||
.await
|
||||
.expect("Failed to delete user_data");
|
||||
// FIXME(zaphar): Migration from local storage to indexed db
|
||||
//self.store
|
||||
// .delete("user_data")
|
||||
// .expect("Failed to delete user_data");
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,7 +323,6 @@ impl LocalStore {
|
||||
/// Sets the set of recipes to the entries passed in. Deletes any recipes not
|
||||
/// in the list.
|
||||
pub async fn set_all_recipes(&self, entries: &Vec<RecipeEntry>) {
|
||||
// FIXME(zaphar): Migration from local storage to indexed db
|
||||
for recipe_key in self.get_recipe_keys().await {
|
||||
let key = to_value(&recipe_key).expect("Failed to serialize key");
|
||||
self.store
|
||||
@ -358,6 +338,7 @@ impl LocalStore {
|
||||
})
|
||||
.await
|
||||
.expect("Failed to delete user_data");
|
||||
// FIXME(zaphar): Migration from local storage to indexed db
|
||||
//self.store
|
||||
// .delete(&recipe_key)
|
||||
// .expect(&format!("Failed to get recipe {}", recipe_key));
|
||||
|
@ -11,13 +11,13 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use anyhow::{Context, Result};
|
||||
use indexed_db::{self, Database, Factory, Transaction};
|
||||
use js_sys::Date;
|
||||
use std::collections::HashSet;
|
||||
use std::future::Future;
|
||||
use tracing::error;
|
||||
use web_sys::{window, Window};
|
||||
use indexed_db::{self, Factory, Database, Transaction};
|
||||
use anyhow::{Result, Context};
|
||||
use std::future::Future;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn get_storage() -> web_sys::Storage {
|
||||
get_window()
|
||||
@ -40,62 +40,91 @@ pub struct DBFactory<'name> {
|
||||
|
||||
impl Default for DBFactory<'static> {
|
||||
fn default() -> Self {
|
||||
DBFactory { name: STATE_STORE_NAME, version: Some(DB_VERSION) }
|
||||
DBFactory {
|
||||
name: STATE_STORE_NAME,
|
||||
version: Some(DB_VERSION),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn version1_setup<'db>(
|
||||
stores: &HashSet<String>,
|
||||
db: &'db Database<std::io::Error>,
|
||||
) -> Result<(), indexed_db::Error<std::io::Error>> {
|
||||
// We use out of line keys for this object store
|
||||
if !stores.contains(STATE_STORE_NAME) {
|
||||
db.build_object_store(STATE_STORE_NAME).create()?;
|
||||
}
|
||||
if !stores.contains(RECIPE_STORE_NAME) {
|
||||
let recipe_store = db.build_object_store(RECIPE_STORE_NAME).create()?;
|
||||
recipe_store
|
||||
.build_index(CATEGORY_IDX, "category")
|
||||
.create()?;
|
||||
recipe_store
|
||||
.build_index(SERVING_COUNT_IDX, "serving_count")
|
||||
.create()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'name> DBFactory<'name> {
|
||||
pub async fn get_indexed_db(&self) -> Result<Database<std::io::Error>> {
|
||||
let factory = Factory::<std::io::Error>::get().context("opening IndexedDB")?;
|
||||
let db = factory.open(self.name, self.version.unwrap_or(0), |evt| async move {
|
||||
// NOTE(zaphar): This is the on upgradeneeded handler. It get's called on new databases or
|
||||
// database with an older version than the one we requested to build.
|
||||
let db = evt.database();
|
||||
let stores = db.object_store_names().into_iter().collect::<HashSet<String>>();
|
||||
// NOTE(jwall): This needs to be somewhat clever in handling version upgrades.
|
||||
if db.version() > 0 {
|
||||
self.version1_setup(&stores, db).await?;
|
||||
}
|
||||
Ok(())
|
||||
}).await.context(format!("Opening or creating the database {}", self.name))?;
|
||||
let db = factory
|
||||
.open(self.name, self.version.unwrap_or(0), |evt| async move {
|
||||
// NOTE(zaphar): This is the on upgradeneeded handler. It get's called on new databases or
|
||||
// databases with an older version than the one we requested to build.
|
||||
let db = evt.database();
|
||||
let stores = db
|
||||
.object_store_names()
|
||||
.into_iter()
|
||||
.collect::<HashSet<String>>();
|
||||
// NOTE(jwall): This needs to be somewhat clever in handling version upgrades.
|
||||
if db.version() > 0 {
|
||||
version1_setup(&stores, db).await?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.context(format!("Opening or creating the database {}", self.name))?;
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
async fn version1_setup<'db>(&self, stores: &HashSet<String>, db: &'db Databse<std::io::Error>) -> std::result::Result<(), std::io::Error> {
|
||||
// We use out of line keys for this object store
|
||||
if !stores.contains(STATE_STORE_NAME) {
|
||||
db.build_object_store(STATE_STORE_NAME).create()?;
|
||||
}
|
||||
if !stores.contains(RECIPE_STORE_NAME) {
|
||||
let recipe_store = db.build_object_store(RECIPE_STORE_NAME).create()?;
|
||||
recipe_store.build_index(CATEGORY_IDX, "category")
|
||||
.create()?;
|
||||
recipe_store.build_index(SERVING_COUNT_IDX, "serving_count")
|
||||
.create()?;
|
||||
}
|
||||
Ok(())
|
||||
pub async fn rw_transaction<Fun, RetFut, Ret>(
|
||||
&self,
|
||||
stores: &[&str],
|
||||
transaction: Fun,
|
||||
) -> indexed_db::Result<Ret, std::io::Error>
|
||||
where
|
||||
Fun: 'static + FnOnce(Transaction<std::io::Error>) -> RetFut,
|
||||
RetFut: 'static + Future<Output = indexed_db::Result<Ret, std::io::Error>>,
|
||||
Ret: 'static,
|
||||
{
|
||||
self.get_indexed_db()
|
||||
.await
|
||||
.expect("Failed to open database")
|
||||
.transaction(stores)
|
||||
.rw()
|
||||
.run(transaction)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn rw_transaction<Fun, RetFut, Ret>(&self, stores: &[&str], transaction: Fun) -> indexed_db::Result<Ret, std::io::Error>
|
||||
where
|
||||
Fun: 'static + FnOnce(Transaction<std::io::Error>) -> RetFut,
|
||||
RetFut: 'static + Future<Output = indexed_db::Result<Ret, std::io::Error>>,
|
||||
Ret: 'static,
|
||||
pub async fn ro_transaction<Fun, RetFut, Ret>(
|
||||
&self,
|
||||
stores: &[&str],
|
||||
transaction: Fun,
|
||||
) -> indexed_db::Result<Ret, std::io::Error>
|
||||
where
|
||||
Fun: 'static + FnOnce(Transaction<std::io::Error>) -> RetFut,
|
||||
RetFut: 'static + Future<Output = indexed_db::Result<Ret, std::io::Error>>,
|
||||
Ret: 'static,
|
||||
{
|
||||
self.get_indexed_db().await.expect("Failed to open database")
|
||||
.transaction(stores).rw()
|
||||
.run(transaction).await
|
||||
}
|
||||
|
||||
pub async fn ro_transaction<Fun, RetFut, Ret>(&self, stores: &[&str], transaction: Fun) -> indexed_db::Result<Ret, std::io::Error>
|
||||
where
|
||||
Fun: 'static + FnOnce(Transaction<std::io::Error>) -> RetFut,
|
||||
RetFut: 'static + Future<Output = indexed_db::Result<Ret, std::io::Error>>,
|
||||
Ret: 'static,
|
||||
{
|
||||
self.get_indexed_db().await.expect("Failed to open database")
|
||||
self.get_indexed_db()
|
||||
.await
|
||||
.expect("Failed to open database")
|
||||
.transaction(stores)
|
||||
.run(transaction).await
|
||||
.run(transaction)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,8 @@ pub fn UI<G: Html>(cx: Scope) -> View<G> {
|
||||
spawn_local_scoped(cx, {
|
||||
async move {
|
||||
let local_store = api::LocalStore::new();
|
||||
// TODO(jwall): At some point we can drop this potentially?
|
||||
local_store.migrate().await;
|
||||
let app_state = if let Some(app_state) = local_store.fetch_app_state().await {
|
||||
app_state
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user