chore: shared memory db with name

This commit is contained in:
kaichaosun 2026-02-05 16:25:28 +08:00
parent 4f6705603d
commit 75fd6acda9
No known key found for this signature in database
GPG Key ID: 223E0F992F4F03BF
3 changed files with 43 additions and 27 deletions

View File

@ -1,26 +1,19 @@
//! Example: Ping-Pong Chat //! Example: Ping-Pong Chat
//! //!
//! This example demonstrates a back-and-forth conversation between two users //! This example demonstrates a back-and-forth conversation between two users
//! using temporary file storage. //! using in-memory storage.
//! //!
//! Run with: cargo run -p logos-chat --example ping_pong //! Run with: cargo run -p logos-chat --example ping_pong
use logos_chat::{ChatManager, StorageConfig}; use logos_chat::ChatManager;
use tempfile::tempdir;
fn main() { fn main() {
println!("=== Ping-Pong Chat Example ===\n"); println!("=== Ping-Pong Chat Example ===\n");
// Create temporary directories for storage // Create two chat participants with in-memory storage
let dir = tempdir().expect("Failed to create temp dir"); // Each ChatManager has its own shared in-memory SQLite database
let alice_db = dir.path().join("alice.db"); let mut alice = ChatManager::in_memory("alice").expect("Failed to create Alice's chat manager");
let bob_db = dir.path().join("bob.db"); let mut bob = ChatManager::in_memory("bob").expect("Failed to create Bob's chat manager");
// Create two chat participants with file-based storage
let mut alice = ChatManager::open(StorageConfig::File(alice_db.to_str().unwrap().to_string()))
.expect("Failed to create Alice's chat manager");
let mut bob = ChatManager::open(StorageConfig::File(bob_db.to_str().unwrap().to_string()))
.expect("Failed to create Bob's chat manager");
println!("Created participants:"); println!("Created participants:");
println!(" Alice: {}", alice.local_address()); println!(" Alice: {}", alice.local_address());

View File

@ -39,6 +39,7 @@ pub enum ChatManagerError {
/// ///
/// It manages identity, inbox, and chats with all state persisted to SQLite. /// It manages identity, inbox, and chats with all state persisted to SQLite.
/// Chats are loaded from storage on each operation - no in-memory caching. /// Chats are loaded from storage on each operation - no in-memory caching.
/// Uses a single shared database for both chat metadata and ratchet state.
/// ///
/// # Example /// # Example
/// ///
@ -68,6 +69,8 @@ pub struct ChatManager {
/// Storage for chat metadata (identity, inbox keys, chat records). /// Storage for chat metadata (identity, inbox keys, chat records).
storage: ChatStorage, storage: ChatStorage,
/// Storage config for creating ratchet storage instances. /// Storage config for creating ratchet storage instances.
/// For file/encrypted databases, SQLite handles connection efficiently.
/// For in-memory testing, use SharedInMemory to share data.
storage_config: StorageConfig, storage_config: StorageConfig,
} }
@ -103,8 +106,14 @@ impl ChatManager {
} }
/// Creates a new in-memory ChatManager (for testing). /// Creates a new in-memory ChatManager (for testing).
pub fn in_memory() -> Result<Self, ChatManagerError> { ///
Self::open(StorageConfig::InMemory) /// Uses a shared in-memory SQLite database so that multiple storage
/// instances within the same ChatManager share data.
///
/// The `db_name` should be unique per ChatManager instance to avoid
/// sharing data between different users.
pub fn in_memory(db_name: &str) -> Result<Self, ChatManagerError> {
Self::open(StorageConfig::SharedInMemory(db_name.to_string()))
} }
/// Creates a new RatchetStorage instance using the stored config. /// Creates a new RatchetStorage instance using the stored config.
@ -334,13 +343,13 @@ mod tests {
#[test] #[test]
fn test_create_chat_manager() { fn test_create_chat_manager() {
let manager = ChatManager::in_memory().unwrap(); let manager = ChatManager::in_memory("test1").unwrap();
assert!(!manager.local_address().is_empty()); assert!(!manager.local_address().is_empty());
} }
#[test] #[test]
fn test_identity_persistence() { fn test_identity_persistence() {
let manager = ChatManager::in_memory().unwrap(); let manager = ChatManager::in_memory("test2").unwrap();
let address = manager.local_address(); let address = manager.local_address();
// Identity should be persisted // Identity should be persisted
@ -351,15 +360,15 @@ mod tests {
#[test] #[test]
fn test_create_intro_bundle() { fn test_create_intro_bundle() {
let mut manager = ChatManager::in_memory().unwrap(); let mut manager = ChatManager::in_memory("test3").unwrap();
let bundle = manager.create_intro_bundle(); let bundle = manager.create_intro_bundle();
assert!(bundle.is_ok()); assert!(bundle.is_ok());
} }
#[test] #[test]
fn test_start_private_chat() { fn test_start_private_chat() {
let mut alice = ChatManager::in_memory().unwrap(); let mut alice = ChatManager::in_memory("alice1").unwrap();
let mut bob = ChatManager::in_memory().unwrap(); let mut bob = ChatManager::in_memory("bob1").unwrap();
// Bob creates an intro bundle // Bob creates an intro bundle
let bob_intro = bob.create_intro_bundle().unwrap(); let bob_intro = bob.create_intro_bundle().unwrap();
@ -379,7 +388,7 @@ mod tests {
#[test] #[test]
fn test_inbox_key_persistence() { fn test_inbox_key_persistence() {
let mut manager = ChatManager::in_memory().unwrap(); let mut manager = ChatManager::in_memory("test4").unwrap();
// Create intro bundle (should persist ephemeral key) // Create intro bundle (should persist ephemeral key)
let intro = manager.create_intro_bundle().unwrap(); let intro = manager.create_intro_bundle().unwrap();
@ -392,8 +401,8 @@ mod tests {
#[test] #[test]
fn test_chat_exists() { fn test_chat_exists() {
let mut alice = ChatManager::in_memory().unwrap(); let mut alice = ChatManager::in_memory("alice2").unwrap();
let mut bob = ChatManager::in_memory().unwrap(); let mut bob = ChatManager::in_memory("bob2").unwrap();
let bob_intro = bob.create_intro_bundle().unwrap(); let bob_intro = bob.create_intro_bundle().unwrap();
let (chat_id, _) = alice.start_private_chat(&bob_intro, "Hello!").unwrap(); let (chat_id, _) = alice.start_private_chat(&bob_intro, "Hello!").unwrap();
@ -405,8 +414,8 @@ mod tests {
#[test] #[test]
fn test_delete_chat() { fn test_delete_chat() {
let mut alice = ChatManager::in_memory().unwrap(); let mut alice = ChatManager::in_memory("alice3").unwrap();
let mut bob = ChatManager::in_memory().unwrap(); let mut bob = ChatManager::in_memory("bob3").unwrap();
let bob_intro = bob.create_intro_bundle().unwrap(); let bob_intro = bob.create_intro_bundle().unwrap();
let (chat_id, _) = alice.start_private_chat(&bob_intro, "Hello!").unwrap(); let (chat_id, _) = alice.start_private_chat(&bob_intro, "Hello!").unwrap();
@ -427,7 +436,7 @@ mod tests {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let db_path = dir.path().join("test.db"); let db_path = dir.path().join("test.db");
let mut bob = ChatManager::in_memory().unwrap(); let mut bob = ChatManager::in_memory("bob4").unwrap();
let bob_intro = bob.create_intro_bundle().unwrap(); let bob_intro = bob.create_intro_bundle().unwrap();
let chat_id; let chat_id;

View File

@ -8,8 +8,11 @@ use crate::StorageError;
/// Configuration for SQLite storage. /// Configuration for SQLite storage.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum StorageConfig { pub enum StorageConfig {
/// In-memory database (for testing). /// In-memory database (isolated, for simple testing).
InMemory, InMemory,
/// Shared in-memory database with a name (multiple connections share data).
/// Use this when you need multiple storage instances to share the same in-memory DB.
SharedInMemory(String),
/// File-based SQLite database. /// File-based SQLite database.
File(String), File(String),
/// SQLCipher encrypted database. /// SQLCipher encrypted database.
@ -29,6 +32,17 @@ impl SqliteDb {
pub fn new(config: StorageConfig) -> Result<Self, StorageError> { pub fn new(config: StorageConfig) -> Result<Self, StorageError> {
let conn = match config { let conn = match config {
StorageConfig::InMemory => Connection::open_in_memory()?, StorageConfig::InMemory => Connection::open_in_memory()?,
StorageConfig::SharedInMemory(ref name) => {
// Use URI mode to create a shared in-memory database
// Multiple connections with the same name share the same data
let uri = format!("file:{}?mode=memory&cache=shared", name);
Connection::open_with_flags(
&uri,
rusqlite::OpenFlags::SQLITE_OPEN_URI
| rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE
| rusqlite::OpenFlags::SQLITE_OPEN_CREATE,
)?
}
StorageConfig::File(ref path) => Connection::open(path)?, StorageConfig::File(ref path) => Connection::open(path)?,
StorageConfig::Encrypted { ref path, ref key } => { StorageConfig::Encrypted { ref path, ref key } => {
let conn = Connection::open(path)?; let conn = Connection::open(path)?;