From dc5be009b74b19a356dcdf8679684389ce6c68d4 Mon Sep 17 00:00:00 2001 From: Tony Date: Sat, 5 Oct 2024 16:21:27 +0800 Subject: [PATCH] Add create or existing --- plugins/store/api-iife.js | 2 +- plugins/store/build.rs | 1 + plugins/store/guest-js/index.ts | 37 +++++++++- .../commands/create_or_existing_store.toml | 13 ++++ .../permissions/autogenerated/reference.md | 26 +++++++ plugins/store/permissions/default.toml | 1 + plugins/store/permissions/schemas/schema.json | 10 +++ plugins/store/src/lib.rs | 56 ++++++++++++--- plugins/store/src/store.rs | 68 ++++++++++++++++--- 9 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml diff --git a/plugins/store/api-iife.js b/plugins/store/api-iife.js index 627d28cbb..af3d90ab7 100644 --- a/plugins/store/api-iife.js +++ b/plugins/store/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,a;function r(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},a){return window.__TAURI_INTERNALS__.invoke(t,e,a)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,a,r){if("a"===a&&!r)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!r:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===a?r:"a"===a?r.call(t):r?r.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,a,r,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,a)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,a){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:r(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.createStore(t,e)}async function c(t){return await u.getStore(t)}e=new WeakMap,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WINDOW_CREATED="tauri://window-created",t.WEBVIEW_CREATED="tauri://webview-created",t.DRAG_ENTER="tauri://drag-enter",t.DRAG_OVER="tauri://drag-over",t.DRAG_DROP="tauri://drag-drop",t.DRAG_LEAVE="tauri://drag-leave"}(a||(a={}));class u extends i{constructor(t){super(t)}static async createStore(t,e){const a=await s("plugin:store|create_store",{path:t,...e});return new u(a)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new u(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,a]=await s("plugin:store|get",{rid:this.rid,key:t});return a?e:void 0}async has(t){return await s("plugin:store|has",{rid:this.rid,key:t})}async delete(t){return await s("plugin:store|delete",{rid:this.rid,key:t})}async clear(){await s("plugin:store|clear",{rid:this.rid})}async reset(){await s("plugin:store|reset",{rid:this.rid})}async keys(){return await s("plugin:store|keys",{rid:this.rid})}async values(){return await s("plugin:store|values",{rid:this.rid})}async entries(){return await s("plugin:store|entries",{rid:this.rid})}async length(){return await s("plugin:store|length",{rid:this.rid})}async load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(a=>{a.payload.resourceId===this.rid&&a.payload.key===t&&e(a.payload.exists?a.payload.value:void 0)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.exists?e.payload.value:void 0)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}return t.LazyStore=class{get store(){return this._store||(this._store=c(this.path).then((async t=>t||await o(this.path,this.options)))),this._store}constructor(t,e){this.path=t,this.options=e}async init(){await this.store}async set(t,e){return(await this.store).set(t,e)}async get(t){return(await this.store).get(t)}async has(t){return(await this.store).has(t)}async delete(t){return(await this.store).delete(t)}async clear(){await(await this.store).clear()}async reset(){await(await this.store).reset()}async keys(){return(await this.store).keys()}async values(){return(await this.store).values()}async entries(){return(await this.store).entries()}async length(){return(await this.store).length()}async load(){await(await this.store).load()}async save(){await(await this.store).save()}async onKeyChange(t,e){return(await this.store).onKeyChange(t,e)}async onChange(t){return(await this.store).onChange(t)}async close(){this._store&&await(await this._store).close()}},t.Store=u,t.createStore=o,t.getStore=c,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,r;function a(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,r,a){if("a"===r&&!a)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!a:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?a:"a"===r?a.call(t):a?a.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,r,a,s){if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,r)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,r){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:a(e)}).then((e=>async()=>async function(t,e){await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await c.createOrExistingStore(t,e)}e=new WeakMap,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WINDOW_CREATED="tauri://window-created",t.WEBVIEW_CREATED="tauri://webview-created",t.DRAG_ENTER="tauri://drag-enter",t.DRAG_OVER="tauri://drag-over",t.DRAG_DROP="tauri://drag-drop",t.DRAG_LEAVE="tauri://drag-leave"}(r||(r={}));class c extends i{constructor(t){super(t)}static async createStore(t,e){const r=await s("plugin:store|create_store",{path:t,...e});return new c(r)}static async createOrExistingStore(t,e){const r=await s("plugin:store|create_or_existing_store",{path:t,...e});return new c(r)}static async getStore(t){const e=await s("plugin:store|get_store",{path:t});return e?new c(e):void 0}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,r]=await s("plugin:store|get",{rid:this.rid,key:t});return r?e:void 0}async has(t){return await s("plugin:store|has",{rid:this.rid,key:t})}async delete(t){return await s("plugin:store|delete",{rid:this.rid,key:t})}async clear(){await s("plugin:store|clear",{rid:this.rid})}async reset(){await s("plugin:store|reset",{rid:this.rid})}async keys(){return await s("plugin:store|keys",{rid:this.rid})}async values(){return await s("plugin:store|values",{rid:this.rid})}async entries(){return await s("plugin:store|entries",{rid:this.rid})}async length(){return await s("plugin:store|length",{rid:this.rid})}async load(){await s("plugin:store|load",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(r=>{r.payload.resourceId===this.rid&&r.payload.key===t&&e(r.payload.exists?r.payload.value:void 0)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.exists?e.payload.value:void 0)}))}async close(){await s("plugin:store|close_store",{rid:this.rid})}}return t.LazyStore=class{get store(){return this._store||(this._store=o(this.path,this.options)),this._store}constructor(t,e){this.path=t,this.options=e}async init(){await this.store}async set(t,e){return(await this.store).set(t,e)}async get(t){return(await this.store).get(t)}async has(t){return(await this.store).has(t)}async delete(t){return(await this.store).delete(t)}async clear(){await(await this.store).clear()}async reset(){await(await this.store).reset()}async keys(){return(await this.store).keys()}async values(){return(await this.store).values()}async entries(){return(await this.store).entries()}async length(){return(await this.store).length()}async load(){await(await this.store).load()}async save(){await(await this.store).save()}async onKeyChange(t,e){return(await this.store).onKeyChange(t,e)}async onChange(t){return(await this.store).onChange(t)}async close(){this._store&&await(await this._store).close()}},t.Store=c,t.createOrExistingStore=o,t.createStore=async function(t,e){return await c.createStore(t,e)},t.getStore=async function(t){return await c.getStore(t)},t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})} diff --git a/plugins/store/build.rs b/plugins/store/build.rs index b22b0b86e..e2530539c 100644 --- a/plugins/store/build.rs +++ b/plugins/store/build.rs @@ -4,6 +4,7 @@ const COMMANDS: &[&str] = &[ "create_store", + "create_or_existing_store", "get_store", "close_store", "set", diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts index 8ffe1bf7e..19b6df955 100644 --- a/plugins/store/guest-js/index.ts +++ b/plugins/store/guest-js/index.ts @@ -45,6 +45,19 @@ export async function createStore( return await Store.createStore(path, options) } +/** + * Create a new Store or get the existing store with the path + * + * @param path: Path to save the store in `app_data_dir` + * @param options: Store configuration options + */ +export async function createOrExistingStore( + path: string, + options?: StoreOptions +): Promise { + return await Store.createOrExistingStore(path, options) +} + /** * @param path: Path of the store in the rust side */ @@ -60,9 +73,7 @@ export class LazyStore implements IStore { private get store(): Promise { if (!this._store) { - this._store = getStore(this.path).then( - async (store) => store || (await createStore(this.path, this.options)) - ) + this._store = createOrExistingStore(this.path, this.options) } return this._store } @@ -183,6 +194,26 @@ export class Store extends Resource implements IStore { ) } + /** + * Create a new Store or get the existing store with the path + * + * @param path: Path to save the store in `app_data_dir` + * @param options: Store configuration options + */ + static async createOrExistingStore( + path: string, + options?: StoreOptions + ): Promise { + const rid = await invoke('plugin:store|create_or_existing_store', { + path, + ...options + }) + return new Store( + rid + // path + ) + } + /** * @param path: Path of the store in the rust side */ diff --git a/plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml b/plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml new file mode 100644 index 000000000..c4555c91c --- /dev/null +++ b/plugins/store/permissions/autogenerated/commands/create_or_existing_store.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-create-or-existing-store" +description = "Enables the create_or_existing_store command without any pre-configured scope." +commands.allow = ["create_or_existing_store"] + +[[permission]] +identifier = "deny-create-or-existing-store" +description = "Denies the create_or_existing_store command without any pre-configured scope." +commands.deny = ["create_or_existing_store"] diff --git a/plugins/store/permissions/autogenerated/reference.md b/plugins/store/permissions/autogenerated/reference.md index ff131d524..6146de310 100644 --- a/plugins/store/permissions/autogenerated/reference.md +++ b/plugins/store/permissions/autogenerated/reference.md @@ -89,6 +89,32 @@ Denies the close_store command without any pre-configured scope. +`store:allow-create-or-existing-store` + + + + +Enables the create_or_existing_store command without any pre-configured scope. + + + + + + + +`store:deny-create-or-existing-store` + + + + +Denies the create_or_existing_store command without any pre-configured scope. + + + + + + + `store:allow-create-store` diff --git a/plugins/store/permissions/default.toml b/plugins/store/permissions/default.toml index 8c2b41c00..fa7264f98 100644 --- a/plugins/store/permissions/default.toml +++ b/plugins/store/permissions/default.toml @@ -12,6 +12,7 @@ All operations are enabled by default. """ permissions = [ "allow-create-store", + "allow-create-or-existing-store", "allow-get-store", "allow-close-store", "allow-clear", diff --git a/plugins/store/permissions/schemas/schema.json b/plugins/store/permissions/schemas/schema.json index 4bd1cee88..277190472 100644 --- a/plugins/store/permissions/schemas/schema.json +++ b/plugins/store/permissions/schemas/schema.json @@ -314,6 +314,16 @@ "type": "string", "const": "deny-close-store" }, + { + "description": "Enables the create_or_existing_store command without any pre-configured scope.", + "type": "string", + "const": "allow-create-or-existing-store" + }, + { + "description": "Denies the create_or_existing_store command without any pre-configured scope.", + "type": "string", + "const": "deny-create-or-existing-store" + }, { "description": "Enables the create_store command without any pre-configured scope.", "type": "string", diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index aecd4362c..1edfa5691 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -57,17 +57,15 @@ enum AutoSave { Bool(bool), } -#[tauri::command] -async fn create_store( +fn builder( app: AppHandle, store_state: State<'_, StoreState>, path: PathBuf, auto_save: Option, serialize_fn_name: Option, deserialize_fn_name: Option, -) -> Result { - let mut builder = app.store_builder(path.clone()); - +) -> Result> { + let mut builder = app.store_builder(path); if let Some(auto_save) = auto_save { match auto_save { AutoSave::DebounceDuration(duration) => { @@ -95,11 +93,51 @@ async fn create_store( .ok_or_else(|| crate::Error::DeserializeFunctionNotFound(deserialize_fn_name))?; builder = builder.deserialize(*deserialize_fn); } + Ok(builder) +} +#[tauri::command] +async fn create_store( + app: AppHandle, + store_state: State<'_, StoreState>, + path: PathBuf, + auto_save: Option, + serialize_fn_name: Option, + deserialize_fn_name: Option, +) -> Result { + let builder = builder( + app, + store_state, + path, + auto_save, + serialize_fn_name, + deserialize_fn_name, + )?; let (_, rid) = builder.build_inner()?; Ok(rid) } +#[tauri::command] +async fn create_or_existing_store( + app: AppHandle, + store_state: State<'_, StoreState>, + path: PathBuf, + auto_save: Option, + serialize_fn_name: Option, + deserialize_fn_name: Option, +) -> Result { + let builder = builder( + app, + store_state, + path, + auto_save, + serialize_fn_name, + deserialize_fn_name, + )?; + let (_, rid) = builder.build_or_existing_inner(); + Ok(rid) +} + #[tauri::command] async fn get_store( app: AppHandle, @@ -107,7 +145,7 @@ async fn get_store( path: PathBuf, ) -> Result> { let stores = store_state.stores.lock().unwrap(); - Ok(stores.get(&resolve_store_path(app, path)).copied()) + Ok(stores.get(&resolve_store_path(&app, path)).copied()) } #[tauri::command] @@ -217,8 +255,7 @@ pub trait StoreExt { impl> StoreExt for T { fn store(&self, path: impl AsRef) -> Arc> { - self.get_store(&path) - .unwrap_or_else(|| self.create_store(&path).unwrap()) + StoreBuilder::new(self.app_handle(), path).build_or_existing() } fn create_store(&self, path: impl AsRef) -> Result>> { @@ -233,7 +270,7 @@ impl> StoreExt for T { let collection = self.state::(); let stores = collection.stores.lock().unwrap(); stores - .get(path.as_ref()) + .get(&resolve_store_path(self.app_handle(), path.as_ref())) .and_then(|rid| self.resources_table().get(*rid).ok()) } } @@ -351,6 +388,7 @@ impl Builder { create_store, get_store, close_store, + create_or_existing_store, set, get, has, diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index ae67fb899..cc11f283f 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -24,10 +24,16 @@ pub type SerializeFn = pub type DeserializeFn = fn(&[u8]) -> Result, Box>; -pub(crate) fn resolve_store_path(app: AppHandle, path: impl AsRef) -> PathBuf { - app.path() - .resolve(path, BaseDirectory::AppData) - .expect("failed to resolve app dir") +pub(crate) fn resolve_store_path( + app: &AppHandle, + path: impl AsRef, +) -> PathBuf { + dunce::simplified( + &app.path() + .resolve(path, BaseDirectory::AppData) + .expect("failed to resolve app dir"), + ) + .to_path_buf() } /// Builds a [`Store`] @@ -55,14 +61,13 @@ impl StoreBuilder { /// ``` pub fn new, P: AsRef>(manager: &M, path: P) -> Self { let app = manager.app_handle().clone(); - let path = resolve_store_path(app.clone(), path); + let path = resolve_store_path(&app, path); let state = app.state::(); let serialize_fn = state.default_serialize; let deserialize_fn = state.default_deserialize; Self { app, - // Since Store.path is only exposed to the user in emit calls we may as well simplify it here already. - path: dunce::simplified(&path).to_path_buf(), + path, defaults: None, serialize_fn, deserialize_fn, @@ -212,7 +217,38 @@ impl StoreBuilder { Ok((store, rid)) } - /// Builds the [`Store`]. + pub(crate) fn build_or_existing_inner(mut self) -> (Arc>, ResourceId) { + let state = self.app.state::(); + let mut stores = state.stores.lock().unwrap(); + + if let Some(rid) = stores.get(&self.path) { + (self.app.resources_table().get(*rid).unwrap(), *rid) + } else { + let mut store_inner = StoreInner::new( + self.app.clone(), + self.path.clone(), + self.defaults.take(), + self.serialize_fn, + self.deserialize_fn, + ); + if self.load_on_build { + let _ = store_inner.load(); + } + + let store = Store { + auto_save: self.auto_save, + auto_save_debounce_sender: Arc::new(Mutex::new(None)), + store: Arc::new(Mutex::new(store_inner)), + }; + + let store = Arc::new(store); + let rid = self.app.resources_table().add_arc(store.clone()); + stores.insert(self.path, rid); + (store, rid) + } + } + + /// Builds the [`Store`], also see [`build_or_existing`](Self::build_or_existing). /// /// This loads the store from disk and put the store in the app's resource table, /// to remove it from the resource table, call [`Store::close_store`] @@ -235,6 +271,22 @@ impl StoreBuilder { let (store, _) = self.build_inner()?; Ok(store) } + + /// Get the existing store with the same path or builds a new [`Store`], also see [`build`](Self::build). + /// + /// # Examples + /// ``` + /// tauri::Builder::default() + /// .plugin(tauri_plugin_store::Builder::default().build()) + /// .setup(|app| { + /// let store = tauri_plugin_store::StoreBuilder::new(app, "store.json").build_or_existing(); + /// Ok(()) + /// }); + /// ``` + pub fn build_or_existing(self) -> Arc> { + let (store, _) = self.build_or_existing_inner(); + store + } } pub(crate) enum AutoSaveMessage {