diff options
Diffstat (limited to 'src/remake/snapshot.rs')
| -rw-r--r-- | src/remake/snapshot.rs | 72 |
1 files changed, 72 insertions, 0 deletions
diff --git a/src/remake/snapshot.rs b/src/remake/snapshot.rs index e82a2ab..2bb5fbb 100644 --- a/src/remake/snapshot.rs +++ b/src/remake/snapshot.rs @@ -289,6 +289,31 @@ pub(crate) fn epoch_metadata_path(generation: u64) -> Vec<String> { meta::path(&[EPOCHS, generation.to_string().as_str()]) } +/// The ended sessions `doc` records (`_continuum/sessions`), by user, each session with +/// until when it would have lasted (one that doesn't read: at once, so it goes). +fn ended_sessions( + doc: &impl ReadDoc, +) -> Result<BTreeMap<String, BTreeMap<String, u64>>, StoreError> { + let mut users = BTreeMap::new(); + for user in store::keys_at(doc, &meta::path(&[meta::SESSIONS]))?.unwrap_or_default() { + let path = meta::path(&[meta::SESSIONS, &user]); + let sessions = store::keys_at(doc, &path)? + .unwrap_or_default() + .into_iter() + .map(|session| { + let until = store::read_plain(doc, &meta::path(&[meta::SESSIONS, &user, &session])) + .ok() + .flatten() + .and_then(|until| until.as_u64()) + .unwrap_or(0); + (session, until) + }) + .collect(); + users.insert(user, sessions); + } + Ok(users) +} + /// Of `earlier` epochs' metadata (by generation), the generations a snapshot of /// `generation`, proposed at `proposed_at`, leaves out: those no rejoining node replays /// through any more. A node on epoch P replays while the replay window of epoch P+1 @@ -377,6 +402,23 @@ pub(crate) fn build( for g in left_out(&earlier, generation, params.proposed_at) { store::delete_path(&mut target, &epoch_metadata_path(g), false)?; } + // Ended sessions past when they would have lasted: their tokens are refused as + // expired anyway. A user's whole map where none is left. + for (user, ended) in ended_sessions(&base)? { + let gone: Vec<&String> = ended + .iter() + .filter(|(_, until)| **until <= params.proposed_at.as_u64()) + .map(|(session, _)| session) + .collect(); + if gone.len() == ended.len() { + store::delete_path(&mut target, &meta::path(&[meta::SESSIONS, &user]), false)?; + } else { + for session in gone { + let path = meta::path(&[meta::SESSIONS, &user, session]); + store::delete_path(&mut target, &path, false)?; + } + } + } // What the kind does to it (a strict remake keeps only what its manifest names). kind.protocol(&base, params.content, &mut target)?; @@ -691,6 +733,36 @@ mod tests { assert_eq!(epoch["baseHeads"].as_array().unwrap().len(), heads.len()); } + /// Ended sessions go once they would have ended anyway; a user's map with them. + #[test] + fn ended_sessions_left_out_once_past() { + let mut a = doc(b"actor-a"); + change(&mut a, |d| { + for (user, session, until) in + [("alice", "s1", 5), ("alice", "s2", 50), ("bob", "s3", 5)] + { + store::write_path( + d, + &meta::path(&[meta::SESSIONS, user, session]), + &json!(until), + ) + .unwrap(); + } + }); + let policy = json!({}); + let heads = a.get_heads(); + let mut at = params(&heads, &policy); + at.proposed_at = UnixMillis(10); + let built = build(&a, &at).unwrap(); + let keys = |path: &[&str]| { + store::keys_at(&built.doc, &meta::path(path)) + .unwrap() + .unwrap_or_default() + }; + assert_eq!(keys(&[meta::SESSIONS]), vec!["alice"]); + assert_eq!(keys(&[meta::SESSIONS, "alice"]), vec!["s2"]); + } + /// Earlier epochs' metadata goes once no rejoining node replays through it, oldest /// first, but for a strict remake's; what doesn't read stays. #[test] |
