1use super::handle::Handle;
2use super::marker::CollectionMarker;
3use super::{DefaultMarker, OnLoadFn, StoreCollection};
4use crate::collection::autosave::Autosave;
5use crate::collection::table::{MarshalerTable, PathTable};
6use crate::error::Result;
7use crate::manager::ManagerExt;
8use crate::migration::{Migration, MigrationContext, Migrator};
9use crate::store::{JsonMarshaler, Marshaler, SaveStrategy, Store, StoreId};
10use dashmap::{DashMap, DashSet};
11use std::collections::HashMap;
12use std::marker::PhantomData;
13use std::path::{Path, PathBuf};
14use std::sync::Mutex;
15use std::time::Duration;
16use tauri::{Manager, Runtime};
17
18#[cfg(feature = "plugin")]
19use tauri::plugin::TauriPlugin;
20
21pub struct StoreCollectionBuilder<R, C>
23where
24 R: Runtime,
25 C: CollectionMarker,
26{
27 default_path: Option<PathBuf>,
28 path_table: HashMap<StoreId, Box<Path>>,
29 default_marshaler: Option<Box<dyn Marshaler>>,
30 marshaler_table: HashMap<StoreId, Box<dyn Marshaler>>,
31 default_save_strategy: SaveStrategy,
32 autosave: Option<Duration>,
33 on_load: Option<Box<OnLoadFn<R, C>>>,
34 save_denylist: DashSet<StoreId>,
35 sync_denylist: DashSet<StoreId>,
36 migrator: Migrator,
37 debug_stores: bool,
38}
39
40impl<R, C> StoreCollectionBuilder<R, C>
41where
42 R: Runtime,
43 C: CollectionMarker,
44{
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 #[must_use]
52 pub fn autosave(mut self, duration: Duration) -> Self {
53 self.autosave = Some(duration);
54 self
55 }
56
57 #[must_use]
59 pub fn default_save_strategy(mut self, strategy: SaveStrategy) -> Self {
60 self.default_save_strategy = strategy;
61 self
62 }
63
64 #[must_use]
66 pub fn on_load<F>(mut self, f: F) -> Self
67 where
68 F: Fn(&Store<R, C>) -> Result<()> + Send + Sync + 'static,
69 {
70 self.on_load = Some(Box::new(f));
71 self
72 }
73
74 #[must_use]
76 pub fn path(mut self, path: impl AsRef<Path>) -> Self {
77 self.default_path = Some(path.as_ref().to_path_buf());
78 self
79 }
80
81 #[must_use]
83 pub fn path_of(mut self, id: impl AsRef<str>, path: impl AsRef<Path>) -> Self {
84 let id = StoreId::from(id.as_ref());
85 let path = Box::from(path.as_ref());
86 self.path_table.insert(id, path);
87 self
88 }
89
90 #[must_use]
92 pub fn save_denylist<I, T>(mut self, denylist: I) -> Self
93 where
94 I: IntoIterator<Item = T>,
95 T: AsRef<str>,
96 {
97 let denylist = denylist
98 .into_iter()
99 .map(|it| StoreId::from(it.as_ref()));
100
101 self.save_denylist.extend(denylist);
102 self
103 }
104
105 #[must_use]
107 pub fn sync_denylist<I, T>(mut self, denylist: I) -> Self
108 where
109 I: IntoIterator<Item = T>,
110 T: AsRef<str>,
111 {
112 let denylist = denylist
113 .into_iter()
114 .map(|it| StoreId::from(it.as_ref()));
115
116 self.sync_denylist.extend(denylist);
117 self
118 }
119
120 #[must_use]
122 pub fn marshaler(mut self, marshaler: Box<dyn Marshaler>) -> Self {
123 self.default_marshaler = Some(marshaler);
124 self
125 }
126
127 #[must_use]
129 pub fn marshaler_of(mut self, id: impl AsRef<str>, marshaler: Box<dyn Marshaler>) -> Self {
130 let id = StoreId::from(id.as_ref());
131 self.marshaler_table.insert(id, marshaler);
132 self
133 }
134
135 #[must_use]
139 pub fn enable_debug_stores(mut self, yes: bool) -> Self {
140 self.debug_stores = yes;
141 self
142 }
143
144 #[must_use]
145 #[doc(hidden)]
146 pub fn migrator(mut self, migrator: Migrator) -> Self {
147 self.migrator = migrator;
148 self
149 }
150
151 #[must_use]
153 pub fn migration(mut self, id: impl Into<StoreId>, migration: Migration) -> Self {
154 self.migrator.add_migration(id.into(), migration);
155 self
156 }
157
158 #[must_use]
160 pub fn migrations<I>(mut self, id: impl Into<StoreId>, migrations: I) -> Self
161 where
162 I: IntoIterator<Item = Migration>,
163 {
164 self
165 .migrator
166 .add_migrations(id.into(), migrations);
167
168 self
169 }
170
171 #[must_use]
173 pub fn on_before_each_migration<F>(mut self, f: F) -> Self
174 where
175 F: Fn(MigrationContext) + Send + Sync + 'static,
176 {
177 self.migrator.on_before_each(f);
178 self
179 }
180
181 #[doc(hidden)]
187 pub fn build(self, handle: Handle<R>, plugin_name: &str) -> Result<()> {
188 let app = handle.app().clone();
189 debug_assert!(
190 app.try_state::<StoreCollection<R, C>>().is_none(),
191 "store collection is already initialized"
192 );
193
194 let default_path = match self.default_path {
195 Some(path) => path,
196 #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
197 None => app.path().app_data_dir()?.join(plugin_name),
198 #[cfg(any(target_os = "android", target_os = "ios"))]
199 None => handle.get_sandboxed_path()?.join(plugin_name),
200 };
201
202 let path_table = PathTable {
203 default: default_path.into_boxed_path(),
204 table: self.path_table,
205 };
206
207 let default_marshaler = self
208 .default_marshaler
209 .unwrap_or_else(|| Box::new(JsonMarshaler));
210
211 let marshaler_table = MarshalerTable {
212 default: default_marshaler,
213 table: self.marshaler_table,
214 };
215
216 app.manage(StoreCollection::<R, C> {
217 handle,
218 name: Box::from(plugin_name),
219 path_table,
220 marshaler_table,
221 stores: DashMap::new(),
222 on_load: self.on_load,
223 autosave: Mutex::new(Autosave::new(self.autosave)),
224 default_save_strategy: self.default_save_strategy,
225 save_denylist: self.save_denylist,
226 sync_denylist: self.sync_denylist,
227 migrator: Mutex::new(self.migrator),
228 debug_stores: self.debug_stores,
229 phantom: PhantomData,
230 });
231
232 let collection = app.store_collection_with_marker::<C>();
233 collection
234 .autosave
235 .lock()
236 .expect("autosave is poisoned")
237 .start::<R, C>(&app);
238
239 collection
240 .migrator
241 .lock()
242 .expect("migrator is poisoned")
243 .read::<R, C>(&app)?;
244
245 Ok(())
246 }
247}
248
249impl<R> StoreCollectionBuilder<R, DefaultMarker>
250where
251 R: Runtime,
252{
253 #[cfg(feature = "plugin")]
259 pub fn build_plugin(self) -> TauriPlugin<R> {
260 crate::plugin::build(self)
261 }
262}
263
264impl<R, C> Default for StoreCollectionBuilder<R, C>
265where
266 R: Runtime,
267 C: CollectionMarker,
268{
269 fn default() -> Self {
270 Self {
271 default_path: None,
272 path_table: HashMap::new(),
273 default_marshaler: None,
274 marshaler_table: HashMap::new(),
275 default_save_strategy: SaveStrategy::Immediate,
276 autosave: None,
277 on_load: None,
278 save_denylist: DashSet::new(),
279 sync_denylist: DashSet::new(),
280 migrator: Migrator::default(),
281 debug_stores: true,
282 }
283 }
284}