mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-09-22 21:30:44 +02:00
refactor(store)!: reset to defaults before merging the on-disk state in reload (#3599)
* refactor(store)!: reset to defaults before merging the on-disk state in reload
`Store::reload` / `reload()` previously merged the on-disk state into the
current in-memory store. It now resets the store to its defaults first,
so in-memory keys that are neither in the defaults nor on disk are
dropped. `reload_ignore_defaults` / `reload({ ignoreDefaults: true })`
is unchanged.
* test(store): cover reload resetting to the defaults before merging the on-disk state
---------
Co-authored-by: Lucas Nogueira <lucas@crabnebula.dev>
This commit is contained in:
co-authored by
Lucas Nogueira
parent
b7897cab5d
commit
b566f09124
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"store": major
|
||||||
|
"store-js": major
|
||||||
|
---
|
||||||
|
|
||||||
|
**Breaking:** `Store::reload` (and `reload()` in JavaScript) now resets the store to its defaults before merging the on-disk state into it, so in-memory keys that are neither in the defaults nor on disk are dropped. Previously the on-disk state was merged into the current in-memory store. Use `reload_ignore_defaults` / `reload({ ignoreDefaults: true })` to fully match the on-disk state.
|
||||||
@@ -35,3 +35,6 @@ tracing = { workspace = true }
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
dunce = { workspace = true }
|
dunce = { workspace = true }
|
||||||
tokio = { version = "1", features = ["sync", "time", "macros"] }
|
tokio = { version = "1", features = ["sync", "time", "macros"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tauri = { workspace = true, features = ["test"] }
|
||||||
|
|||||||
@@ -405,9 +405,9 @@ interface IStore {
|
|||||||
* This method is useful if the on-disk state was edited by the user and you want to synchronize the changes.
|
* This method is useful if the on-disk state was edited by the user and you want to synchronize the changes.
|
||||||
*
|
*
|
||||||
* Note:
|
* Note:
|
||||||
* - This method loads the data and merges it with the current store,
|
* - This method resets the store to its defaults and then merges the on-disk state into it,
|
||||||
* this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
|
* so keys that are neither in the defaults nor on disk are dropped.
|
||||||
* to fully match the store with the on-disk state, set {@linkcode ReloadOptions | ignoreDefaults} to `true`
|
* To fully match the store with the on-disk state (ignoring defaults), set {@linkcode ReloadOptions | ignoreDefaults} to `true`
|
||||||
* - This method does not emit change events.
|
* - This method does not emit change events.
|
||||||
*
|
*
|
||||||
* @returns
|
* @returns
|
||||||
|
|||||||
@@ -300,12 +300,13 @@ impl<R: Runtime> StoreInner<R> {
|
|||||||
|
|
||||||
/// Update the store from the on-disk state
|
/// Update the store from the on-disk state
|
||||||
///
|
///
|
||||||
/// Note: This method loads the data and merges it with the current store
|
/// Note: This method resets the store to its defaults and then merges the on-disk state into it
|
||||||
pub fn load(&mut self) -> crate::Result<()> {
|
pub fn load(&mut self) -> crate::Result<()> {
|
||||||
let bytes = fs::read(&self.path)?;
|
let bytes = fs::read(&self.path)?;
|
||||||
|
let entries = (self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?;
|
||||||
|
|
||||||
self.cache
|
self.cache = self.defaults.clone().unwrap_or_default();
|
||||||
.extend((self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?);
|
self.cache.extend(entries);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -525,9 +526,9 @@ impl<R: Runtime> Store<R> {
|
|||||||
/// Update the store from the on-disk state
|
/// Update the store from the on-disk state
|
||||||
///
|
///
|
||||||
/// Note:
|
/// Note:
|
||||||
/// - This method loads the data and merges it with the current store,
|
/// - This method resets the store to its defaults and then merges the on-disk state into it,
|
||||||
/// this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
|
/// so keys that are neither in the defaults nor on disk are dropped.
|
||||||
/// to fully match the store with the on-disk state,
|
/// To fully match the store with the on-disk state (ignoring defaults),
|
||||||
/// use [`reload_ignore_defaults`](Self::reload_ignore_defaults) instead
|
/// use [`reload_ignore_defaults`](Self::reload_ignore_defaults) instead
|
||||||
/// - This method does not emit change events
|
/// - This method does not emit change events
|
||||||
pub fn reload(&self) -> crate::Result<()> {
|
pub fn reload(&self) -> crate::Result<()> {
|
||||||
@@ -613,3 +614,74 @@ impl<R: Runtime> Drop for Store<R> {
|
|||||||
self.apply_pending_auto_save();
|
self.apply_pending_auto_save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde_json::json;
|
||||||
|
use tauri::{
|
||||||
|
test::{mock_app, MockRuntime},
|
||||||
|
App,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn temp_store_path(name: &str) -> PathBuf {
|
||||||
|
let dir =
|
||||||
|
std::env::temp_dir().join(format!("tauri-plugin-store-{}-{name}", std::process::id()));
|
||||||
|
fs::create_dir_all(&dir).unwrap();
|
||||||
|
dir.join("store.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store(app: &App<MockRuntime>, path: PathBuf) -> StoreInner<MockRuntime> {
|
||||||
|
let mut defaults = HashMap::new();
|
||||||
|
defaults.insert("default-key".to_string(), json!("default"));
|
||||||
|
defaults.insert("shared-key".to_string(), json!("default"));
|
||||||
|
StoreInner::new(
|
||||||
|
app.handle().clone(),
|
||||||
|
path,
|
||||||
|
Some(defaults),
|
||||||
|
crate::default_serialize,
|
||||||
|
crate::default_deserialize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_resets_to_defaults_before_merging_the_on_disk_state() {
|
||||||
|
let app = mock_app();
|
||||||
|
let path = temp_store_path("load");
|
||||||
|
let mut store = store(&app, path.clone());
|
||||||
|
fs::write(&path, r#"{ "disk-key": "disk", "shared-key": "disk" }"#).unwrap();
|
||||||
|
// neither in the defaults nor on disk, so it must be dropped
|
||||||
|
store
|
||||||
|
.cache
|
||||||
|
.insert("memory-key".to_string(), json!("memory"));
|
||||||
|
|
||||||
|
store.load().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(store.get("default-key"), Some(&json!("default")));
|
||||||
|
assert_eq!(store.get("disk-key"), Some(&json!("disk")));
|
||||||
|
// the on-disk state takes precedence over the defaults
|
||||||
|
assert_eq!(store.get("shared-key"), Some(&json!("disk")));
|
||||||
|
assert_eq!(store.get("memory-key"), None);
|
||||||
|
|
||||||
|
let _ = fs::remove_dir_all(path.parent().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_ignore_defaults_matches_the_on_disk_state() {
|
||||||
|
let app = mock_app();
|
||||||
|
let path = temp_store_path("load-ignore-defaults");
|
||||||
|
let mut store = store(&app, path.clone());
|
||||||
|
fs::write(&path, r#"{ "disk-key": "disk" }"#).unwrap();
|
||||||
|
store
|
||||||
|
.cache
|
||||||
|
.insert("memory-key".to_string(), json!("memory"));
|
||||||
|
|
||||||
|
store.load_ignore_defaults().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(store.get("disk-key"), Some(&json!("disk")));
|
||||||
|
assert_eq!(store.get("default-key"), None);
|
||||||
|
assert_eq!(store.get("memory-key"), None);
|
||||||
|
|
||||||
|
let _ = fs::remove_dir_all(path.parent().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user