use std::sync::{Arc, RwLock}; use anyhow::Error; use cozo::DbInstance; use tracing::{error, debug}; use crate::comm::{Peer, CommHandle, messages::{Message, MessageContent}}; use self::types::{ElementContent, ElementId, Element, Tag}; pub mod types; mod queries; mod schema; pub struct State { db: DbInstance, comm_handle: RwLock>>, } impl State { pub async fn new() -> anyhow::Result> { let db = DbInstance::new("mem", "", Default::default()); match db { Ok(d) => { schema::add_schema(&d)?; Ok(Arc::new(State {db: d, comm_handle: RwLock::new(None)})) }, Err(e) => Err(Error::msg(format!("{:?}", e))), } } pub fn set_comm_handle(&self, handle: Arc) { *self.comm_handle.write().as_deref_mut().expect("Could not set state's CommHandle") = Some(handle); } // Create an element and add it to the database pub fn create_element(&self, content: &ElementContent) -> anyhow::Result { let id = ElementId::new(); queries::set_element(&self.db, &id, &content)?; debug!("Created element with id {:?}: {:?}", &id, self.get_element(&id)); self.send_to_peers(MessageContent::CreateElement { id: id.clone(), content: content.clone() }); Ok(id) } // Anyone updated an element, update it in the database pub fn set_element(&self, element_id: &ElementId, content: &ElementContent) -> anyhow::Result<()> { let res = queries::set_element(&self.db, element_id, content); debug!("Set element with id {:?}: {:?}", element_id, self.get_element(element_id)); self.send_to_peers(MessageContent::SetElement { id: element_id.clone(), content: content.clone() }); res } pub fn set_element_content(&self, element_id: &ElementId, content: &ElementContent) -> anyhow::Result<()> { let res = queries::set_element_content(&self.db, element_id, content); debug!("Set element content with id {:?}: {:?}", element_id, self.get_element(element_id)); self.send_to_peers(MessageContent::SetElement { id: element_id.clone(), content: content.clone() }); res } pub fn remove_element(&self, element_id: &ElementId) -> anyhow::Result<()> { let res = queries::remove_element(&self.db, element_id); self.send_to_peers(MessageContent::RemoveElement { id: element_id.clone() }); res } pub fn get_element(&self, id: &ElementId) -> Option { queries::get_element(&self.db, id).ok() } pub fn get_elements_by_tag(&self, tag: &Tag) -> Vec { queries::get_elements_by_tag(&self.db, tag) .map_err(|e| {error!("{}", e); e}) .unwrap_or(vec![]) } pub fn set_peer(&self, peer: &Peer) -> anyhow::Result<()> { queries::add_peer(&self.db, &peer.id(), &peer.name()) } pub fn get_peers(&self) -> anyhow::Result> { queries::get_peers(&self.db) } fn send_to_peers(&self, ct: MessageContent) { match self.comm_handle.read() { Ok(opt) => { if opt.is_some() { let arc = opt.as_ref().unwrap().clone(); tokio::spawn(async move { let _ = arc.broadcast(Message::new(ct)).await; }); } }, Err(e) => debug!("{}", e), } } } #[cfg(test)] mod tests { use crate::state::State; use crate::state::types::ElementContent; #[tokio::test] #[serial_test::serial] async fn test_create() { tracing_subscriber::fmt().pretty().init(); let state = State::new().await.unwrap(); let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap(); let el = state.get_element(&id).unwrap(); assert_eq!( ElementContent::Text("Test-text".to_string()), el.content().to_owned() ) } #[tokio::test] #[serial_test::serial] async fn test_update() { tracing_subscriber::fmt().pretty().init(); let state = State::new().await.unwrap(); let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap(); state.set_element(&id,&ElementContent::Text("Test-text 2".to_string())).unwrap(); let el = state.get_element(&id).unwrap(); assert_eq!( ElementContent::Text("Test-text 2".to_string()), el.content().to_owned() ) } }