use std::{collections::HashMap, time::SystemTime};
use parking_lot::RwLock;
use super::id::ClientId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SyncMode {
#[default]
Independent,
Follow {
target: ClientId,
},
Present,
}
#[derive(Debug, Clone)]
pub struct ClientPresence {
pub client_id: ClientId,
pub client_type: String,
pub display_name: String,
pub buffer_id: Option<usize>,
pub cursor: (usize, usize),
pub visible_lines: (usize, usize),
pub mode: String,
pub sync_mode: SyncMode,
pub joined_at: SystemTime,
}
impl ClientPresence {
#[must_use]
pub fn new(
client_id: ClientId,
client_type: impl Into<String>,
display_name: impl Into<String>,
) -> Self {
Self {
client_id,
client_type: client_type.into(),
display_name: display_name.into(),
buffer_id: None,
cursor: (0, 0),
visible_lines: (0, 24),
mode: "NORMAL".to_string(),
sync_mode: SyncMode::default(),
joined_at: SystemTime::now(),
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)] pub fn joined_at_ms(&self) -> u64 {
self.joined_at
.duration_since(SystemTime::UNIX_EPOCH)
.map_or(0, |d| d.as_millis() as u64)
}
}
#[derive(Debug, Default)]
pub struct PresenceMap {
clients: RwLock<HashMap<ClientId, ClientPresence>>,
}
impl PresenceMap {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn join(&self, presence: ClientPresence) -> Vec<ClientPresence> {
let mut clients = self.clients.write();
let peers: Vec<_> = clients.values().cloned().collect();
clients.insert(presence.client_id, presence);
peers
}
pub fn leave(&self, client_id: ClientId) -> Option<ClientPresence> {
self.clients.write().remove(&client_id)
}
pub fn update<F>(&self, client_id: ClientId, f: F) -> Option<ClientPresence>
where
F: FnOnce(&mut ClientPresence),
{
let mut clients = self.clients.write();
clients.get_mut(&client_id).map(|presence| {
f(presence);
presence.clone()
})
}
#[must_use]
pub fn get(&self, client_id: ClientId) -> Option<ClientPresence> {
self.clients.read().get(&client_id).cloned()
}
#[must_use]
pub fn list(&self) -> Vec<ClientPresence> {
self.clients.read().values().cloned().collect()
}
#[must_use]
pub fn followers_of(&self, target_id: ClientId) -> Vec<ClientId> {
self.clients
.read()
.values()
.filter_map(|p| match p.sync_mode {
SyncMode::Follow { target } if target == target_id => Some(p.client_id),
_ => None,
})
.collect()
}
#[must_use]
pub fn contains(&self, client_id: ClientId) -> bool {
self.clients.read().contains_key(&client_id)
}
#[must_use]
pub fn len(&self) -> usize {
self.clients.read().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.clients.read().is_empty()
}
}
#[cfg(test)]
#[path = "presence_tests.rs"]
mod tests;