diff --git a/crates/notebook/src/live/share.rs b/crates/notebook/src/live/share.rs index 1c0ca9025c5b18a4e40a5f7eb1b7d3463e6c00de..d9b06c802bcac13a9068ec57f761ac5d10553b30 100644 --- a/crates/notebook/src/live/share.rs +++ b/crates/notebook/src/live/share.rs @@ -215,8 +215,9 @@ pub struct Host { reach: Option, relay: Option, sharing: Mutex, - room: Live, - pairing: Mutex, + /// The share's room and the code's, until it stops. + room: Mutex>, + pairing: Mutex>, served: Arc, events: Arc, } @@ -269,20 +270,21 @@ impl Host { notebook: notebook.to_owned(), reach, relay: relay.map(str::to_owned), - pairing: Mutex::new(pair(&me, &sharing, notebook, reach, relay, &events)?), + pairing: Mutex::new(Some(pair(&me, &sharing, notebook, reach, relay, &events)?)), me, sharing: Mutex::new(sharing), - room, + room: Mutex::new(Some(room)), served, events, }; Ok(host) } - /// The code guests type, once it has its number. A code with too many wrong tries is - /// replaced by one with new words. + /// The code guests type, once it has its number, and none once stopped. A code with too + /// many wrong tries is replaced by one with new words. pub fn code(&self) -> Option { let mut pairing = self.pairing.lock().unwrap(); + let pairing = pairing.as_mut()?; if pairing.burned() { let mut sharing = self.sharing.lock().unwrap(); sharing.code = words().ok()?; @@ -312,16 +314,20 @@ impl Host { /// How the relay last answered the code's room. pub fn relayed(&self) -> Relayed { - self.pairing.lock().unwrap().relayed() + let pairing = self.pairing.lock().unwrap(); + pairing.as_ref().map_or(Relayed::Unknown, Live::relayed) } /// The peers in the share's room. pub fn guests(&self) -> Vec { - self.room.peers() + let room = self.room.lock().unwrap(); + room.as_ref().map(Live::peers).unwrap_or_default() } pub fn set_presence(&self, presence: Presence) { - self.room.set_presence(presence); + if let Some(room) = &*self.room.lock().unwrap() { + room.set_presence(presence); + } } /// Tells every guest the files at these catalog paths changed. @@ -329,10 +335,13 @@ impl Host { self.served.tell(paths); } - /// Stops sharing: every guest hears so and is let go. - pub fn stop(self) { - drop(self.pairing); - self.room.leave(STOPPED); + /// Stops sharing: no one new is welcomed, and every guest hears so and is let go. + pub fn stop(&self) { + drop(self.pairing.lock().unwrap().take()); + let room = self.room.lock().unwrap().take(); + if let Some(room) = room { + room.leave(STOPPED); + } } } diff --git a/crates/notebook/tests/live_share.rs b/crates/notebook/tests/live_share.rs index 268c76faa5fcc94d61a0d14d799182a10089aad2..f9b571d6a424c5eab94916dde3e9e7b0df90e067 100644 --- a/crates/notebook/tests/live_share.rs +++ b/crates/notebook/tests/live_share.rs @@ -506,3 +506,28 @@ fn a_flooding_guest_is_hung_up_on() { std::fs::read(folder.join("Garden.one")).unwrap() ); } + +/// Stopping a share lets every guest go, saying so, and leaves its code and secret for no one: +/// the code no longer opens anything, and sharing again makes new ones. +#[test] +fn stopping_lets_every_guest_go_and_retires_the_code() { + let directory = tempfile::tempdir().unwrap(); + let folder = notebook(directory.path()); + let url = relay(Default::default()); + let sharing = Sharing::new("").unwrap(); + let host = host(&folder, &directory.path().join("host"), &sharing, &url); + let code = code(&host); + let (guest, _notebook) = guest("Grace", &code, &url, &directory.path().join("grace")); + host.stop(); + until("the guest never heard the host stop", || { + guest.stopped() && guest.host().is_none() + }); + assert!(host.code().is_none() && host.guests().is_empty()); + assert!(matches!( + share::join(hello("Alan"), &code, "", None, Some(&url)).unwrap_err(), + Refusal::NoOne | Refusal::TimedOut + )); + let again = Sharing::new("").unwrap(); + assert!(again.secret != sharing.secret && again.share != sharing.share); + assert_ne!(again.code, sharing.code); +} diff --git a/crates/snowbound/src/live.rs b/crates/snowbound/src/live.rs index 1d573b7e7a071e0cb4a3c905293e506ffbea17a0..c34418286839f8b82cb4156d84c2a6cd4946574d 100644 --- a/crates/snowbound/src/live.rs +++ b/crates/snowbound/src/live.rs @@ -474,11 +474,14 @@ impl State { background.on_touched(None); } if let Some(host) = self.peers.hosts.remove(location) { - crate::spawn(move || { - if let Some(host) = Arc::into_inner(host) { - host.stop(); - } - }); + crate::spawn(move || host.stop()); + } + // Kept, the share would start again on the next frame, as after a relaunch. + if let Some(sharing) = &mut self.peers.sharing + && sharing.remove(location).is_some() + && let Err(error) = keep(&kept(&self.cache, HOSTING), sharing) + { + eprintln!("Keeping what this computer shares: {error}"); } } diff --git a/crates/snowbound/tests/replay.rs b/crates/snowbound/tests/replay.rs index 79ca0e308730a3001109642b6767aa49fa738d2e..151f02b0b4cde9eabedc913015e9515fdab01722 100644 --- a/crates/snowbound/tests/replay.rs +++ b/crates/snowbound/tests/replay.rs @@ -33,6 +33,16 @@ impl Drop for Scratch { /// returns the trees each /// `accessibility` step named by the trailing word wrote. fn replay(scratch: &Scratch, notebook: Option<&Path>, steps: &[&str]) -> Vec { + replay_with(scratch, notebook, steps, &[]) +} + +/// `replay` with these variables set for the app. +fn replay_with( + scratch: &Scratch, + notebook: Option<&Path>, + steps: &[&str], + variables: &[(&str, &str)], +) -> Vec { let dir = &scratch.0; if let Some(source) = notebook { for entry in std::fs::read_dir(source).unwrap() { @@ -59,6 +69,7 @@ fn replay(scratch: &Scratch, notebook: Option<&Path>, steps: &[&str]) -> Vec