use std::cell::RefCell;
use std::collections::BTreeSet;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Suspension {
depth: u32,
changed: BTreeSet<String>,
}
thread_local! {
static SUSPENSION: RefCell<Suspension> = RefCell::default();
}
pub fn suspend() {
SUSPENSION.with_borrow_mut(|s| {
s.depth += 1;
if s.depth == 1 {
s.changed.clear();
}
});
}
pub fn resume() -> crate::TViewResult<()> {
SUSPENSION.with_borrow_mut(|s| {
if s.depth == 0 {
return Err(crate::TViewError::WrongState {
reason: "Cannot resume: not suspended".to_string(),
});
}
s.depth -= 1;
Ok(())
})
}
#[must_use]
pub fn is_suspended() -> bool {
SUSPENSION.with_borrow(|s| s.depth > 0)
}
pub fn record_change(entity_name: &str) {
SUSPENSION.with_borrow_mut(|s| {
if s.depth > 0 {
s.changed.insert(entity_name.to_string());
}
});
}
#[must_use]
pub fn get_changed_entities() -> Vec<String> {
SUSPENSION.with_borrow(|s| s.changed.iter().cloned().collect())
}
pub fn clear_changed_entities() {
SUSPENSION.with_borrow_mut(|s| s.changed.clear());
}
pub fn snapshot() -> Suspension {
SUSPENSION.with_borrow(Clone::clone)
}
pub fn restore(saved: Suspension) {
SUSPENSION.with_borrow_mut(|s| *s = saved);
}
pub fn catch_up() -> crate::TViewResult<Vec<String>> {
let changed = get_changed_entities();
clear_changed_entities();
if changed.is_empty() {
return Ok(Vec::new());
}
crate::admin::rebuild_with_dependents(&changed)
}
pub fn force_resume() {
SUSPENSION.with_borrow_mut(|s| s.depth = 0);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_restored_snapshot_undoes_suspend_and_resume() {
force_resume();
clear_changed_entities();
let before = snapshot();
suspend();
record_change("post");
assert!(is_suspended());
restore(before);
assert!(!is_suspended());
assert!(get_changed_entities().is_empty());
suspend();
let suspended = snapshot();
resume().unwrap();
assert!(!is_suspended());
restore(suspended);
assert!(is_suspended());
force_resume();
}
#[test]
fn resume_without_suspend_is_refused() {
force_resume();
assert!(resume().is_err());
}
}