tauri_plugin_persistence/
desktop.rs1use std::{collections::HashMap, str::FromStr, sync::Arc};
2
3use serde::{de::DeserializeOwned, Serialize};
4use tauri::{plugin::PluginApi, AppHandle, Manager, Runtime, State};
5use tokio::sync::Mutex;
6
7use crate::{api::types::{CollectionSpecifier, ContextSpecifier, DatabaseSpecifier, FileHandleSpecifier}, state::{ContextState, PluginState}};
8
9pub fn init<R: Runtime, C: DeserializeOwned>(
10 app: &AppHandle<R>,
11 _api: PluginApi<R, C>,
12) -> crate::Result<Persistence<R>> {
13 Ok(Persistence(app.clone()))
14}
15
16pub struct Persistence<R: Runtime>(AppHandle<R>);
18
19impl<R: Runtime> Persistence<R> {
20 fn contexts(&self) -> State<'_, PluginState> {
21 self.0.state::<PluginState>().clone()
22 }
23
24 fn handle(&self) -> AppHandle<R> {
25 self.0.clone()
26 }
27
28 async fn context_ids(&self) -> Vec<String> {
29 let ctx = self.contexts();
30 let contexts = ctx.lock().await;
31 contexts.keys().map(|k| k.clone()).collect()
32 }
33
34 pub async fn open_context(&self, name: impl AsRef<str>, path: impl AsRef<str>) -> crate::Result<crate::Context<R>> {
37 let ctx = self.contexts();
38 let resolved_path = std::path::PathBuf::from_str(path.as_ref()).or(Err(crate::Error::invalid_path(path.as_ref())))?;
39 let mut contexts = ctx.lock().await;
40
41 if let Some(ctx) = contexts.get(&name.as_ref().to_string()) {
42 if ctx.root_path == path.as_ref().to_string() {
43 Ok(crate::Context::<R>::create(self.handle(), name.as_ref().to_string(), path.as_ref().to_string()))
44 } else {
45 Err(crate::Error::open_context(name, path, "Context already open at a different path."))
46 }
47 } else {
48 if resolved_path.exists() {
49 if resolved_path.is_dir() {
50 let _ = contexts.insert(name.as_ref().to_string(), ContextState {name: name.as_ref().to_string(), root_path: path.as_ref().to_string(), databases: Arc::new(Mutex::new(HashMap::new())), files: Arc::new(Mutex::new(HashMap::new()))});
51 Ok(crate::Context::<R>::create(self.handle(), name.as_ref().to_string(), path.as_ref().to_string()))
52 } else {
53 Err(crate::Error::open_context(name, path, "Specified path is not a directory."))
54 }
55 } else {
56 tokio::fs::create_dir_all(resolved_path).await.or_else(|e| Err(crate::Error::open_context(name.as_ref(), path.as_ref(), format!("Failed to create context directory: {e:?}"))))?;
57 let _ = contexts.insert(name.as_ref().to_string(), ContextState {name: name.as_ref().to_string(), root_path: path.as_ref().to_string(), databases: Arc::new(Mutex::new(HashMap::new())), files: Arc::new(Mutex::new(HashMap::new()))});
58 Ok(crate::Context::<R>::create(self.handle(), name.as_ref().to_string(), path.as_ref().to_string()))
59 }
60 }
61 }
62
63 pub async fn aliased_context(&self, name: impl AsRef<str>) -> crate::Result<crate::Context<R>> {
65 if let Some(ctx) = self.contexts().lock().await.get(&name.as_ref().to_string()) {
66 Ok(crate::Context::<R>::create(self.handle(), name.as_ref().to_string(), ctx.root_path.clone()))
67 } else {
68 Err(crate::Error::unknown_context(name))
69 }
70 }
71
72 pub async fn context(&self, context: ContextSpecifier) -> crate::Result<crate::Context<R>> {
74 match context {
75 ContextSpecifier::Aliased { alias } => self.aliased_context(alias).await,
76 ContextSpecifier::Direct { alias, path } => self.open_context(alias, path).await
77 }
78 }
79
80 pub async fn database(&self, context: ContextSpecifier, database: DatabaseSpecifier) -> crate::Result<crate::Database<R>> {
82 let context = self.context(context).await?;
83 match database {
84 DatabaseSpecifier::Aliased { alias } => context.database(alias).await,
85 DatabaseSpecifier::Direct { alias, path } => context.open_database(alias, path).await
86 }
87 }
88
89 pub async fn file_handle(&self, context: ContextSpecifier, file_handle: FileHandleSpecifier) -> crate::Result<crate::FileHandle<R>> {
91 let context = self.context(context).await?;
92 match file_handle {
93 FileHandleSpecifier::Aliased { id } => context.file_handle(id).await,
94 FileHandleSpecifier::Direct { path, mode } => context.open_file_handle(path, mode).await
95 }
96 }
97
98 pub async fn transaction(&self, context: ContextSpecifier, database: DatabaseSpecifier, transaction: bson::Uuid) -> crate::Result<crate::Transaction<R>> {
100 let database = self.database(context, database).await?;
101 database.get_transaction(transaction).await
102 }
103
104 pub async fn collection<T: Serialize + DeserializeOwned + Sync + Send>(&self, context: ContextSpecifier, database: DatabaseSpecifier, collection: CollectionSpecifier) -> crate::Result<crate::Collection<T, R>> {
106 let database = self.database(context, database).await?;
107 match collection {
108 CollectionSpecifier::Global { name } => Ok(database.collection(name).await),
109 CollectionSpecifier::Transaction { transaction, name } => {
110 let trn = database.get_transaction(transaction).await?;
111 Ok(trn.collection(name))
112 }
113 }
114 }
115
116 pub async fn close_context(&self, context: ContextSpecifier) -> crate::Result<()> {
118 let ctx = match context {
119 ContextSpecifier::Aliased { alias } => self.aliased_context(alias).await,
120 ContextSpecifier::Direct { alias, path } => self.open_context(alias, path).await
121 }?;
122 let cid = ctx.name();
123 ctx.close().await?;
124 let ctxs = self.contexts();
125 let mut contexts = ctxs.lock().await;
126 let _ = contexts.remove(&cid);
127 Ok(())
128 }
129
130 pub async fn cleanup(&self) -> crate::Result<()> {
132 for context in self.context_ids().await {
133 self.close_context(ContextSpecifier::Aliased { alias: context }).await?;
134 }
135
136 Ok(())
137 }
138}