use_prelude!();
use std::{
cmp::Ordering,
sync::{Mutex, Weak},
};
use serde::{Deserialize, Serialize};
use super::peers_observer::{PeersObserver, PeersObserverCtx};
pub(crate) struct PresenceManagerV2Context {
observers: Mutex<Vec<Weak<PeersObserverCtx>>>,
}
impl PresenceManagerV2Context {
pub(crate) fn new() -> Self {
Self {
observers: Mutex::new(Vec::new()),
}
}
pub(crate) fn add_observer(
self: &Arc<Self>,
ditto: Arc<BoxedDitto>,
handler: impl Fn(V2Presence) + Send + Sync + 'static,
) -> PeersObserver {
let context = PeersObserverCtx::new(Box::new(handler));
let arc_context = Arc::new(context);
let arc_context_1 = arc_context.retain();
let weak_context = Arc::downgrade(&arc_context_1);
let need_register_callback;
{
let mut observers = self.observers.lock().unwrap();
observers.push(weak_context);
need_register_callback = observers.len() == 1;
}
if need_register_callback {
let weak_self = Arc::downgrade(self);
let raw_self = weak_self.into_raw() as *mut _;
unsafe {
ffi_sdk::ditto_register_presence_v2_callback(
&ditto,
raw_self,
None,
None,
Some(PresenceManagerV2Context::on_event),
);
}
}
let self_1 = self.retain();
std::thread::spawn(move || {
unsafe {
let str_box = ffi_sdk::ditto_presence_v2(&ditto);
self_1.on_presence(str_box.to_str())
};
});
PeersObserver::new(arc_context)
}
pub(crate) fn parse_presence(presence_json_str: &str) -> Result<V2Presence, String> {
serde_json::from_str(presence_json_str)
.map_err(|e| format!("Error deserializing V2Presence, error = {:?}", e))
}
pub(crate) fn on_presence(&self, presence_json_str: &str) {
let event = match Self::parse_presence(presence_json_str) {
Ok(event) => event,
Err(e) => {
::log::error!("Error deserializing V2Presence, error = {:?}", e);
return;
}
};
let mut observers = self.observers.lock().unwrap();
for i in (0..observers.len()).rev() {
let observer = &observers[i];
if let Some(observer) = observer.upgrade() {
(observer.on_presence)(event.clone());
} else {
observers.remove(i);
}
}
}
pub(crate) unsafe extern "C" fn on_event(ctx: *mut c_void, json: char_p::Ref<'_>) {
let weak_ctx = Weak::from_raw(ctx as *const PresenceManagerV2Context);
if let Some(strong_ctx) = weak_ctx.upgrade() {
let presence_json_str = json.to_str();
strong_ctx.on_presence(presence_json_str);
}
let _ = weak_ctx.into_raw();
}
}
pub(crate) type NetworkId = u32;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct V2Presence {
pub local_peer: NetworkId,
pub peers: Vec<V2Peer>,
pub connections: Vec<V2UndirectedConnection>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct V2Peer {
pub id: NetworkId,
pub site_id: Option<String>,
pub device_name: String,
pub os: Option<V2Os>,
#[serde(default)]
pub query_overlap_group: u8,
#[deprecated(note = "Use `query_overlap_group` instead")]
#[serde(default)]
pub mesh_role: u8,
pub is_hydra_connected: bool,
pub is_compatible: Option<bool>,
pub ditto_sdk_version: Option<String>,
}
impl Eq for V2Peer {}
impl PartialEq<Self> for V2Peer {
fn eq(&self, other: &Self) -> bool {
self.id.eq(&other.id)
}
}
impl PartialOrd for V2Peer {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for V2Peer {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct V2UndirectedConnection {
pub id: String,
pub from: NetworkId,
pub to: NetworkId,
pub connection_type: V2ConnectionType,
pub approximate_distance_in_meters: Option<f32>,
}
impl Eq for V2UndirectedConnection {}
impl PartialEq<Self> for V2UndirectedConnection {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl PartialOrd for V2UndirectedConnection {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for V2UndirectedConnection {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
#[derive(PartialEq, Eq, Hash, Clone, Debug, Serialize, Deserialize)]
pub enum V2Os {
#[serde(rename = "Generic")]
Generic,
#[serde(rename = "iOS")]
Ios,
#[serde(rename = "Android")]
Android,
#[serde(rename = "Linux")]
Linux,
#[serde(rename = "Windows")]
Windows,
#[serde(rename = "macOS")]
MacOS,
}
#[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum V2ConnectionType {
Bluetooth,
AccessPoint,
P2PWiFi,
WebSocket,
}
#[cfg(test)]
mod tests {
use super::*;
const V2_PEER_JSON: &str = r#"
{
"localPeer": 1,
"peers": [
{
"id": 1,
"siteId": "1",
"deviceName": "local-peer",
"os": "macOS",
"isHydraConnected": false,
"isCompatible": true,
"dittoSdkVersion": "1.0.0",
"meshRole": 0,
"queryOverlapGroup": 0
},
{
"id": 2,
"siteId": "2",
"deviceName": "device-2",
"os": "iOS",
"isHydraConnected": false,
"isCompatible": true,
"dittoSdkVersion": null,
"meshRole": 0,
"queryOverlapGroup": 0
},
{
"id": 3,
"siteId": "3",
"deviceName": "device-3",
"os": "Android",
"isHydraConnected": false,
"isCompatible": true,
"dittoSdkVersion": "1.0.3",
"meshRole": 32,
"queryOverlapGroup": 32
},
{
"id": 4,
"siteId": "4",
"deviceName": "device-4",
"os": "Linux",
"isHydraConnected": false,
"isCompatible": true,
"dittoSdkVersion": null
}
],
"connections": [
{
"id": "1<->2:Bluetooth",
"from": 1,
"to": 2,
"connectionType": "Bluetooth",
"approximateDistanceInMeters": 2.2963063716888428
},
{
"id": "1<->3:AccessPoint",
"from": 1,
"to": 3,
"connectionType": "AccessPoint",
"approximateDistanceInMeters": null
},
{
"id": "1<->4:WebSocket",
"from": 1,
"to": 4,
"connectionType": "WebSocket",
"approximateDistanceInMeters": null
}
]
}
"#;
#[test]
fn test_json_parsing() {
let presence = PresenceManagerV2Context::parse_presence(V2_PEER_JSON).unwrap();
let peers = presence.peers;
assert_eq!(peers.len(), 4);
assert_eq!(peers[0].id, 1);
assert_eq!(peers[0].device_name, "local-peer");
assert_eq!(peers[0].os, Some(V2Os::MacOS));
assert_eq!(peers[0].query_overlap_group, 0);
assert_eq!(peers[1].id, 2);
assert_eq!(peers[1].device_name, "device-2");
assert_eq!(peers[1].os, Some(V2Os::Ios));
assert_eq!(peers[1].query_overlap_group, 0);
assert_eq!(peers[2].id, 3);
assert_eq!(peers[2].device_name, "device-3");
assert_eq!(peers[2].os, Some(V2Os::Android));
assert_eq!(peers[2].query_overlap_group, 32);
assert_eq!(peers[3].id, 4);
assert_eq!(peers[3].device_name, "device-4");
assert_eq!(peers[3].os, Some(V2Os::Linux));
assert_eq!(peers[3].query_overlap_group, 0);
#[allow(deprecated)]
{
assert_eq!(peers[0].mesh_role, 0); assert_eq!(peers[1].mesh_role, 0); assert_eq!(peers[2].mesh_role, 32); assert_eq!(peers[3].mesh_role, 0); }
}
}