feat(presence): live friends listener + ownership move (W7 B2)
Makes the friends list live, building on the B1 persistent endpoint.
Friends ownership moves into core (was the GUI's):
- Core loads/owns friends.json behind a shared Mutex<FriendStore>; a malformed
load yields an empty store flagged READ-ONLY so we never overwrite the damaged
file (fixes backlog A16). New commands AddFriend/RemoveFriend/RenameFriend +
UiEvent::FriendsUpdated{friends,read_only}; the GUI is now a read-only mirror
that renders from the event and drives mutations via commands. The friends UI
shows a warning + blocks edits when read-only.
- Presence posture pushed to core via SetPresenceMode (persistence stays in
AppConfig); held in a shared Mutex for the listener/scheduler.
Live listener + outbound scheduler:
- New FriendsProtocol ProtocolHandler on the persistent Router for FRIENDS_ALPN
(the router owns accept(), so the listener can't be presence_net::serve — same
delegation pattern as B1's AudioRouter). Its reply policy reads the shared
friends/mode/current-room and uses presence::should_answer: answer friends only,
never while invisible, and report our current gathering's restamped member
ticket so a friend can one-click Join. handle()'s body is factored into a shared
exchange() used by both serve (tests) and FriendsProtocol.
- Outbound ping scheduler folded into the core loop via tokio::select! on a slow
interval (60s, first pass delayed 3s for endpoint online). FULLY DARK while
Invisible (no probing at all — user's choice). Each pass runs detached so it
never blocks command handling and picks up a rebuilt stack next tick; probes
friends with a saved addr in parallel and emits UiEvent::FriendPresence.
- note_seen auto-heal: a connected peer who is a friend has their last_addr
refreshed (+persisted) so the scheduler can reach them later.
- current_room shared state set on Join (restamped ticket) / cleared on Leave.
P5 UI: each friend shows online / offline / in-room with a one-click Join.
256 lib + 6 reconnect + 4 loopback + 2 ignored real-endpoint tests green, clippy
--all-targets clean, release builds. B2a (ownership/A16) is solo-verifiable; the
live listener + scheduler need the 2-machine field test before this merges.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+25
-1
@@ -1,6 +1,8 @@
|
||||
use crate::config::{NetworkMode, RecordingMode};
|
||||
use crate::friends::Friend;
|
||||
use crate::network::PeerState;
|
||||
use iroh::EndpointId;
|
||||
use crate::presence::{FriendPresence, PresenceMode};
|
||||
use iroh::{EndpointAddr, EndpointId};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CoreCommand {
|
||||
@@ -52,6 +54,18 @@ pub enum CoreCommand {
|
||||
/// on the next room join (the endpoint is rebuilt then). The core replies with
|
||||
/// an updated [`UiEvent::IdentityStatus`].
|
||||
RegenerateIdentity,
|
||||
/// Add a friend (W7). Core owns the friends store: it mutates + persists it and
|
||||
/// replies with [`UiEvent::FriendsUpdated`]. `addr` seeds `last_addr` if known
|
||||
/// (e.g. added from a room). Idempotent — re-adding an existing id is a no-op.
|
||||
AddFriend { id: EndpointId, name: String, addr: Option<EndpointAddr> },
|
||||
/// Remove a friend by id (W7).
|
||||
RemoveFriend(EndpointId),
|
||||
/// Locally rename a friend (W7).
|
||||
RenameFriend(EndpointId, String),
|
||||
/// Set our presence posture (W7). Gates the idle listener (answer friends-only /
|
||||
/// invisible) and the outbound ping scheduler (invisible = fully dark). Sent at
|
||||
/// startup from config and whenever the user changes it.
|
||||
SetPresenceMode(PresenceMode),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -89,5 +103,15 @@ pub enum UiEvent {
|
||||
/// since the id (and thus friend recognition) won't survive the next launch.
|
||||
/// `error` carries the reason when degraded, for the UI explainer.
|
||||
IdentityStatus { node_id: String, persisted: bool, error: Option<String> },
|
||||
/// The friends list (W7), now owned by core. Sent at startup (after load) and
|
||||
/// after every add/remove/rename so the GUI renders from this snapshot instead
|
||||
/// of owning the store. `read_only` is true when `friends.json` failed to load
|
||||
/// (malformed) — the GUI shows a degraded warning and disables edits so we never
|
||||
/// overwrite the damaged file (backlog A16).
|
||||
FriendsUpdated { friends: Vec<Friend>, read_only: bool },
|
||||
/// A friend's live presence from a successful ping reply (W7): online, or in a
|
||||
/// joinable gathering (with a one-click ticket). Emitted by the outbound ping
|
||||
/// scheduler; absence of a recent event = treat as offline.
|
||||
FriendPresence { id: EndpointId, presence: FriendPresence },
|
||||
Error(String),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user