stop forwarding data to remote when no friend is online

This commit is contained in:
2026-09-05 02:09:15 +08:00
parent 8a80bf5f59
commit 295ba679dd
3 changed files with 89 additions and 3 deletions
+6 -2
View File
@@ -173,7 +173,9 @@ async fn connect(
changed = cursor_data.changed() => {
changed.map_err(|_| "cursor sender closed")?;
let payload = { cursor_data.borrow_and_update().clone() };
if let Some(payload) = payload {
if let Some(payload) = payload
&& friend_presence.has_online_friends(&remote.id)?
{
send(&mut writer, &ClientMessage::Signed {
signature: keypair.sign(&message_bytes(&payload)),
payload,
@@ -183,7 +185,9 @@ async fn connect(
changed = foreground_app_data.changed() => {
changed.map_err(|_| "foreground-app sender closed")?;
let payload = { foreground_app_data.borrow_and_update().clone() };
if let Some(payload) = payload {
if let Some(payload) = payload
&& friend_presence.has_online_friends(&remote.id)?
{
send(&mut writer, &ClientMessage::Signed {
signature: keypair.sign(&message_bytes(&payload)),
payload,
+12 -1
View File
@@ -95,6 +95,14 @@ impl Network {
}
pub fn send_live_data(&self, data: LiveData) {
let active_remotes = match self.friend_presence.active_remotes() {
Ok(remotes) if remotes.is_empty() => return,
Ok(remotes) => remotes,
Err(error) => {
eprintln!("failed to read friend presence for live data: {error}");
return;
}
};
let sequence = match data.kind() {
LiveDataKind::Cursor => self.next_cursor_sequence.fetch_add(1, Ordering::Relaxed),
LiveDataKind::ForegroundApp => self
@@ -114,7 +122,10 @@ impl Network {
eprintln!("failed to lock remote connections for live data");
return;
};
for connection in connections.values() {
for (remote_id, connection) in connections.iter() {
if !active_remotes.contains(remote_id) {
continue;
}
match envelope.kind() {
LiveDataKind::Cursor => {
connection.cursor_sender.send_replace(Some(payload.clone()));
+71
View File
@@ -14,6 +14,16 @@ pub(super) struct Change {
pub(super) struct FriendPresence(Mutex<HashMap<String, HashSet<String>>>);
impl FriendPresence {
pub(super) fn active_remotes(&self) -> Result<HashSet<String>, String> {
let by_remote = self.0.lock().map_err(|error| error.to_string())?;
Ok(by_remote.keys().cloned().collect())
}
pub(super) fn has_online_friends(&self, remote_id: &str) -> Result<bool, String> {
let by_remote = self.0.lock().map_err(|error| error.to_string())?;
Ok(by_remote.contains_key(remote_id))
}
pub(super) fn remotes_for(&self, friend_id: &str) -> Result<HashSet<String>, String> {
let by_remote = self.0.lock().map_err(|error| error.to_string())?;
Ok(by_remote
@@ -128,6 +138,67 @@ fn diff(
mod tests {
use super::{Change, FriendPresence};
#[test]
fn live_data_routes_follow_each_remotes_online_friends() {
let presence = FriendPresence::default();
assert!(presence.active_remotes().unwrap().is_empty());
assert!(!presence.has_online_friends("remote-a").unwrap());
presence
.replace("remote-a", vec!["friend-a".into(), "friend-b".into()])
.unwrap();
presence.replace("remote-b", Vec::new()).unwrap();
assert_eq!(
presence.active_remotes().unwrap(),
["remote-a".to_owned()].into()
);
assert!(!presence.has_online_friends("remote-b").unwrap());
presence
.update("remote-a", "friend-a".into(), false)
.unwrap();
assert!(presence.has_online_friends("remote-a").unwrap());
presence
.update("remote-a", "friend-b".into(), false)
.unwrap();
assert!(!presence.has_online_friends("remote-a").unwrap());
assert!(presence.active_remotes().unwrap().is_empty());
let change = presence
.update("remote-b", "friend-a".into(), true)
.unwrap()
.unwrap();
assert!(
change.route_added,
"resuming must trigger current-state publication"
);
assert!(presence.has_online_friends("remote-b").unwrap());
assert!(!presence.has_online_friends("remote-a").unwrap());
}
#[test]
fn disconnect_snapshot_and_friend_removal_disable_live_data_routes() {
let presence = FriendPresence::default();
for remote in ["disconnected", "empty-snapshot", "removed-friend"] {
presence.update(remote, "friend".into(), true).unwrap();
}
presence.remove("disconnected").unwrap();
presence.replace("empty-snapshot", Vec::new()).unwrap();
assert!(!presence.has_online_friends("disconnected").unwrap());
assert!(!presence.has_online_friends("empty-snapshot").unwrap());
assert!(presence.has_online_friends("removed-friend").unwrap());
presence.retain(&[]).unwrap();
assert!(presence.active_remotes().unwrap().is_empty());
assert!(!presence.has_online_friends("removed-friend").unwrap());
let change = presence
.replace("disconnected", vec!["friend".into()])
.unwrap()
.unwrap();
assert!(change.route_added);
assert!(presence.has_online_friends("disconnected").unwrap());
}
#[test]
fn friend_stays_online_while_any_remote_reports_presence() {
let presence = FriendPresence::default();