Skip to main content

tauri_plugin_velesdb/
state.rs

1//! State management for the `VelesDB` Tauri plugin.
2//!
3//! Manages the database instance and provides thread-safe access
4//! to collections across Tauri commands.
5
6use std::path::PathBuf;
7use std::sync::Arc;
8
9use parking_lot::RwLock;
10use velesdb_core::Database;
11
12use crate::error::{Error, Result};
13
14/// Plugin state holding the database instance.
15///
16/// This struct is managed by Tauri and provides thread-safe access
17/// to the `VelesDB` database from all commands.
18pub struct VelesDbState {
19    /// The database instance wrapped in Arc<RwLock> for thread-safe access.
20    db: Arc<RwLock<Option<Arc<Database>>>>,
21    /// Path to the database directory.
22    path: PathBuf,
23}
24
25impl VelesDbState {
26    /// Creates a new plugin state with the specified database path.
27    ///
28    /// # Arguments
29    ///
30    /// * `path` - Path to the database directory
31    ///
32    /// # Returns
33    ///
34    /// A new `VelesDbState` instance (database not yet opened).
35    #[must_use]
36    pub fn new(path: PathBuf) -> Self {
37        Self {
38            db: Arc::new(RwLock::new(None)),
39            path,
40        }
41    }
42
43    /// Opens the database, creating it if it doesn't exist.
44    ///
45    /// # Errors
46    ///
47    /// Returns an error if the database cannot be opened.
48    pub fn open(&self) -> Result<()> {
49        let mut db_guard = self.db.write();
50        if db_guard.is_none() {
51            let db = Arc::new(Database::open(&self.path)?);
52            *db_guard = Some(db);
53            tracing::info!("VelesDB opened at {:?}", self.path);
54        }
55        Ok(())
56    }
57
58    /// Returns a reference to the database, opening it if necessary.
59    ///
60    /// # Errors
61    ///
62    /// Returns an error if the database cannot be accessed.
63    pub fn get_db(&self) -> Result<Arc<RwLock<Option<Arc<Database>>>>> {
64        // Ensure database is open
65        {
66            let db_guard = self.db.read();
67            if db_guard.is_none() {
68                drop(db_guard);
69                self.open()?;
70            }
71        }
72        Ok(Arc::clone(&self.db))
73    }
74
75    /// Executes a function with read access to the database.
76    ///
77    /// # Errors
78    ///
79    /// Returns an error if the database is not available.
80    pub fn with_db<F, T>(&self, f: F) -> Result<T>
81    where
82        F: FnOnce(Arc<Database>) -> Result<T>,
83    {
84        self.open()?;
85        let db_guard = self.db.read();
86        let db = db_guard
87            .as_ref()
88            .ok_or_else(|| Error::InvalidConfig("Database not initialized".to_string()))?;
89        f(Arc::clone(db))
90    }
91
92    /// Returns the database path.
93    #[must_use]
94    pub fn path(&self) -> &PathBuf {
95        &self.path
96    }
97}
98
99impl Default for VelesDbState {
100    fn default() -> Self {
101        Self::new(PathBuf::from("./velesdb_data"))
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use tempfile::tempdir;
109
110    #[test]
111    fn test_state_new() {
112        // Arrange
113        let path = PathBuf::from("/tmp/test_db");
114
115        // Act
116        let state = VelesDbState::new(path.clone());
117
118        // Assert
119        assert_eq!(state.path(), &path);
120    }
121
122    #[test]
123    fn test_state_default() {
124        // Act
125        let state = VelesDbState::default();
126
127        // Assert
128        assert_eq!(state.path(), &PathBuf::from("./velesdb_data"));
129    }
130
131    #[test]
132    fn test_state_open_and_access() {
133        // Arrange
134        let dir = tempdir().expect("Failed to create temp dir");
135        let state = VelesDbState::new(dir.path().to_path_buf());
136
137        // Act
138        let result = state.open();
139
140        // Assert
141        assert!(result.is_ok());
142    }
143
144    #[test]
145    fn test_state_with_db() {
146        // Arrange
147        let dir = tempdir().expect("Failed to create temp dir");
148        let state = VelesDbState::new(dir.path().to_path_buf());
149
150        // Act
151        let result = state.with_db(|db| {
152            // Just verify we can access the database
153            let collections = db.list_collections();
154            Ok(collections.len())
155        });
156        // Note: db is Arc<Database> — list_collections() is reachable via Deref
157
158        // Assert
159        assert!(result.is_ok());
160        assert_eq!(result.unwrap(), 0); // No collections initially
161    }
162
163    #[test]
164    fn test_state_multiple_opens_idempotent() {
165        // Arrange
166        let dir = tempdir().expect("Failed to create temp dir");
167        let state = VelesDbState::new(dir.path().to_path_buf());
168
169        // Act - open multiple times
170        let result1 = state.open();
171        let result2 = state.open();
172        let result3 = state.open();
173
174        // Assert - all should succeed
175        assert!(result1.is_ok());
176        assert!(result2.is_ok());
177        assert!(result3.is_ok());
178    }
179}