mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-03-27 06:33:08 +00:00
feat: persist identity
This commit is contained in:
parent
f4c08bd048
commit
37eb2749b2
@ -13,6 +13,8 @@ use safer_ffi::{
|
||||
prelude::{c_slice, repr_c},
|
||||
};
|
||||
|
||||
use storage::StorageConfig;
|
||||
|
||||
use crate::{
|
||||
context::{Context, Introduction},
|
||||
errors::ChatError,
|
||||
@ -54,6 +56,26 @@ pub fn create_context(name: repr_c::String) -> repr_c::Box<ContextHandle> {
|
||||
Box::new(ContextHandle(Context::new_with_name(&*name))).into()
|
||||
}
|
||||
|
||||
/// Creates a new libchat Context with file-based persistent storage.
|
||||
///
|
||||
/// The identity will be loaded from storage if it exists, or created and saved if not.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - name: Friendly name for the identity (used if creating new identity)
|
||||
/// - db_path: Path to the SQLite database file
|
||||
///
|
||||
/// # Returns
|
||||
/// Opaque handle to the context. Must be freed with destroy_context()
|
||||
#[ffi_export]
|
||||
pub fn create_context_with_storage(
|
||||
name: repr_c::String,
|
||||
db_path: repr_c::String,
|
||||
) -> repr_c::Box<ContextHandle> {
|
||||
let config = StorageConfig::File(db_path.to_string());
|
||||
let ctx = Context::open(&*name, config).expect("failed to open context with storage");
|
||||
Box::new(ContextHandle(ctx)).into()
|
||||
}
|
||||
|
||||
/// Returns the friendly name of the contexts installation.
|
||||
///
|
||||
#[ffi_export]
|
||||
|
||||
@ -1,34 +1,73 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use storage::StorageConfig;
|
||||
|
||||
use crate::{
|
||||
conversation::{ConversationId, ConversationStore, Convo, Id},
|
||||
errors::ChatError,
|
||||
identity::Identity,
|
||||
inbox::Inbox,
|
||||
proto::{EncryptedPayload, EnvelopeV1, Message},
|
||||
storage::{ChatStorage, StorageError},
|
||||
types::{AddressedEnvelope, ContentData},
|
||||
};
|
||||
|
||||
pub use crate::conversation::ConversationIdOwned;
|
||||
pub use crate::inbox::Introduction;
|
||||
|
||||
/// Error type for Context operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ContextError {
|
||||
#[error("chat error: {0}")]
|
||||
Chat(#[from] ChatError),
|
||||
|
||||
#[error("storage error: {0}")]
|
||||
Storage(#[from] StorageError),
|
||||
}
|
||||
|
||||
// This is the main entry point to the conversations api.
|
||||
// Ctx manages lifetimes of objects to process and generate payloads.
|
||||
pub struct Context {
|
||||
_identity: Rc<Identity>,
|
||||
store: ConversationStore,
|
||||
inbox: Inbox,
|
||||
storage: ChatStorage,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new_with_name(name: impl Into<String>) -> Self {
|
||||
let identity = Rc::new(Identity::new(name));
|
||||
let inbox = Inbox::new(Rc::clone(&identity)); //
|
||||
Self {
|
||||
/// Opens or creates a Context with the given storage configuration.
|
||||
///
|
||||
/// If an identity exists in storage, it will be restored.
|
||||
/// Otherwise, a new identity will be created with the given name and saved.
|
||||
pub fn open(name: impl Into<String>, config: StorageConfig) -> Result<Self, ContextError> {
|
||||
let mut storage = ChatStorage::new(config)?;
|
||||
let name = name.into();
|
||||
|
||||
// Load or create identity
|
||||
let identity = if let Some(identity) = storage.load_identity()? {
|
||||
identity
|
||||
} else {
|
||||
let identity = Identity::new(&name);
|
||||
storage.save_identity(&identity)?;
|
||||
identity
|
||||
};
|
||||
|
||||
let identity = Rc::new(identity);
|
||||
let inbox = Inbox::new(Rc::clone(&identity));
|
||||
|
||||
Ok(Self {
|
||||
_identity: identity,
|
||||
store: ConversationStore::new(),
|
||||
inbox,
|
||||
}
|
||||
storage,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new in-memory Context (for testing).
|
||||
///
|
||||
/// Uses in-memory SQLite database. Each call creates a new isolated database.
|
||||
pub fn new_with_name(name: impl Into<String>) -> Self {
|
||||
Self::open(name, StorageConfig::InMemory).expect("in-memory storage should not fail")
|
||||
}
|
||||
|
||||
pub fn installation_name(&self) -> &str {
|
||||
@ -195,4 +234,31 @@ mod tests {
|
||||
send_and_verify(&mut saro, &mut raya, &saro_convo_id, &content);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_persistence() {
|
||||
// Use file-based storage to test real persistence
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let db_path = dir
|
||||
.path()
|
||||
.join("test_identity.db")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let config = StorageConfig::File(db_path);
|
||||
|
||||
// Create context - this should create and save a new identity
|
||||
let ctx1 = Context::open("alice", config.clone()).unwrap();
|
||||
let pubkey1 = ctx1._identity.public_key();
|
||||
let name1 = ctx1.installation_name().to_string();
|
||||
|
||||
// Drop and reopen - should load the same identity
|
||||
drop(ctx1);
|
||||
let ctx2 = Context::open("alice", config).unwrap();
|
||||
let pubkey2 = ctx2._identity.public_key();
|
||||
let name2 = ctx2.installation_name().to_string();
|
||||
|
||||
// Identity should be the same
|
||||
assert_eq!(pubkey1, pubkey2, "public key should persist");
|
||||
assert_eq!(name1, name2, "name should persist");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user