diff --git a/ubisync/src/comm/mod.rs b/ubisync/src/comm/mod.rs index 6a2138f..6698745 100644 --- a/ubisync/src/comm/mod.rs +++ b/ubisync/src/comm/mod.rs @@ -160,6 +160,10 @@ impl CommHandle { Ok(i2p_dest) } + pub fn own_peer_id(&self) -> anyhow::Result { + Ok(self.i2p_address()?.into()) + } + fn read_connection( wrapped_stream: Arc>, state: Arc, diff --git a/ubisync/src/lib.rs b/ubisync/src/lib.rs index 9f7a906..4a52048 100644 --- a/ubisync/src/lib.rs +++ b/ubisync/src/lib.rs @@ -61,14 +61,19 @@ impl Ubisync { pub fn add_peer(&self, p: impl TryInto) -> anyhow::Result<()> { match p.try_into() { - Ok(peer) => self.state_handle.set_peer(peer), + Ok(peer) => self.state_handle.add_peer(peer), Err(e) => bail!(e), } } pub fn add_peer_from_id(&self, id: PeerId) -> anyhow::Result<()> { // TODO: resolve peer's name before setting - self.state_handle.set_peer(Peer::new(id, None)) + self.state_handle.add_peer(Peer::new(id, None)) + } + + pub fn add_family_member_from_id(&self, id: PeerId) -> anyhow::Result<()> { + self.add_peer_from_id(id.clone())?; + self.state_handle.add_family_member(id) } pub fn get_apps(&self) -> Vec { diff --git a/ubisync/src/state/database/collections/peer_families.rs b/ubisync/src/state/database/collections/peer_families.rs index 182dfdd..2cd6880 100644 --- a/ubisync/src/state/database/collections/peer_families.rs +++ b/ubisync/src/state/database/collections/peer_families.rs @@ -1,7 +1,11 @@ use std::collections::HashSet; use anyhow::{anyhow, Error}; -use bonsaidb::core::schema::{Collection, SerializedCollection}; +use bonsaidb::core::{ + connection::Connection, + document::Emit, + schema::{view::map::Mappings, Collection, MapReduce, SerializedCollection, View, ViewSchema}, +}; use itertools::Itertools; use serde::{Deserialize, Serialize}; use ubisync_lib::types::{Family, FamilyId, PeerId}; @@ -9,7 +13,7 @@ use ubisync_lib::types::{Family, FamilyId, PeerId}; use crate::state::database::{as_key::AsKey, StateDB}; #[derive(Debug, Serialize, Deserialize, Collection, PartialEq, Clone)] -#[collection(name = "peer-families", views = [])] +#[collection(name = "peer-families", views = [DbPeerFamilyByMemberId])] pub(super) struct DbPeerFamily { #[natural_id] pub(super) id: AsKey, @@ -17,6 +21,33 @@ pub(super) struct DbPeerFamily { pub(super) members: HashSet, } +#[derive(Debug, Clone, View, ViewSchema)] +#[view(collection = DbPeerFamily, key = AsKey, value = Option, name = "by-member-id")] +pub(super) struct DbPeerFamilyByMemberId; + +impl MapReduce for DbPeerFamilyByMemberId { + fn map<'doc>( + &self, + document: &'doc bonsaidb::core::document::BorrowedDocument<'_>, + ) -> bonsaidb::core::schema::ViewMapResult<'doc, Self> { + let content = DbPeerFamily::document_contents(document)?; + Ok(content + .members + .iter() + .fold(Mappings::default(), |acc, member| { + acc.and( + document + .header + .emit_key_and_value( + AsKey::new(member.to_owned()), + Some((*content.id).clone()), + ) + .unwrap_or(Mappings::none()), + ) + })) + } +} + impl From for Family { fn from(value: DbPeerFamily) -> Self { Family { @@ -75,6 +106,19 @@ impl StateDB { .map(|doc_opt| doc_opt.map(|doc| doc.contents.into())) .map_err(|e| anyhow!(e)) } + + pub fn get_family_of_peer(&self, peer: PeerId) -> anyhow::Result> { + self.db + .view::() + .with_key(&AsKey::new(peer)) + .query_with_collection_docs() + .map(|results| match results.mappings.len() { + 0 => Ok(None), + 1 => Ok(results.mappings.first().unwrap().value.clone()), + _ => Err(Error::msg("Peer appears to be member of multiple families")), + })? + .map_err(|e| anyhow!(e)) + } } #[cfg(test)] @@ -141,4 +185,20 @@ mod tests { )) ); } + + #[test] + fn get_family_of_peer() { + let db = StateDB::init(None); + let family_id = FamilyId::new(); + let peer_id = PeerId::default(); + + db.add_peer_family( + family_id.clone(), + Some("My family name".to_string()), + vec![peer_id.clone()], + ) + .unwrap(); + + assert_eq!(db.get_family_of_peer(peer_id).unwrap(), Some(family_id)) + } } diff --git a/ubisync/src/state/mod.rs b/ubisync/src/state/mod.rs index 78e0804..fd04812 100644 --- a/ubisync/src/state/mod.rs +++ b/ubisync/src/state/mod.rs @@ -10,7 +10,7 @@ use ubisync_lib::{ api::events::AppEvent, messages::{Message, MessageContent}, peer::Peer, - types::{AppId, Element, ElementContent, ElementId, PotId, Tag}, + types::{AppId, Element, ElementContent, ElementId, FamilyId, PeerId, PotId, Tag}, }; use anyhow::Error; @@ -101,7 +101,7 @@ impl State { todo!() } - pub fn set_peer(&self, peer: Peer) -> anyhow::Result<()> { + pub fn add_peer(&self, peer: Peer) -> anyhow::Result<()> { self.db.add_peer(peer) } @@ -127,6 +127,28 @@ impl State { }) } + pub fn add_family_member(&self, peer: PeerId) -> anyhow::Result<()> { + let my_id = self + .comm_handle + .read() + .map_err(|_| Error::msg("Failed to lock on CommHandle"))? + .to_owned() + .ok_or(Error::msg("CommHandle not initialized"))? + .own_peer_id()?; + + if self.db.get_family_of_peer(my_id.clone())?.is_none() { + self.db + .add_peer_family(FamilyId::new(), None, vec![my_id.clone()])?; + } + + self.db.add_peer_to_family( + peer, + self.db + .get_family_of_peer(my_id)? + .ok_or(Error::msg("Could not find own family"))?, + ) + } + pub fn get_event_receiver( &self, app: &AppId, diff --git a/ubisync/tests/api.rs b/ubisync/tests/api.rs index 1fb75b3..dbcb0b8 100644 --- a/ubisync/tests/api.rs +++ b/ubisync/tests/api.rs @@ -23,7 +23,7 @@ async fn two_nodes_element_creation() { c2.api_config.port = Some(9982); let ubi1 = Ubisync::new(&Config::default()).await.unwrap(); let ubi2 = Arc::new(Ubisync::new(&c2).await.unwrap()); - ubi1.add_peer_from_id(ubi2.get_destination().unwrap().into()) + ubi1.add_family_member_from_id(ubi2.get_destination().unwrap().into()) .unwrap(); let api_client1 = @@ -92,7 +92,7 @@ async fn two_nodes_api_event() { c2.api_config.port = Some(9982); let ubi1 = Arc::new(Ubisync::new(&Config::default()).await.unwrap()); let ubi2 = Ubisync::new(&c2).await.unwrap(); - ubi2.add_peer_from_id(ubi1.get_destination().unwrap().into()) + ubi2.add_family_member_from_id(ubi1.get_destination().unwrap().into()) .unwrap(); let api_client1 =