Keeping the service's copy of the parents' log as long as the household says: the same setting a device applies to its
own (HouseholdConfig::log_retention_days; none, the default, keeps everything). The lines older than that are
emptied (never removed: the counts devices sync by are made of the lines' numbers), the pictures only they showed are
deleted, and a Cleared line is written to the log as the device service, which every device pulls like any other
line, so nothing leaves the log unannounced.
The name the service writes its own lines under.
13pub const SERVICE_DEVICE: &str = "service";
Clears what the household's setting says is too old, as of now_ms. Returns how many lines were emptied.
16pub fn apply_retention(store: &Store, household: &SqliteHouseholdHub, now_ms: u64) -> usize { 17 let Ok(doc) = whiskers_ports::run_ready(household.current()) else { 18 error!("retention: the household's choices cannot be read"); 19 return 0; 20 }; 21 let Some(days) = doc.config().log_retention_days else { return 0 }; 22 let cutoff = now_ms.saturating_sub(u64::from(days) * 86_400_000); 23 let log = SqliteLog::new(store.clone(), SERVICE_DEVICE); 24 match log.expire_before(cutoff) { 25 Ok(0) => 0, 26 Ok(n) => { 27 info!("retention: {n} line(s) older than {days} day(s) were emptied"); 28 if let Err(e) = log.clone().append(&Entry::new(now_ms, Event::Cleared { lines: n, older_than_ms: cutoff })) { 29 warn!("retention: {n} line(s) were emptied but the record of it could not be written: {}", e.0); 30 } 31 n 32 } 33 Err(e) => { 34 error!("retention: the log could not be cleared: {}", e.0); 35 0 36 } 37 } 38}
40#[cfg(test)] 41mod tests { 42 use super::*; 43 use whiskers_core::{HouseholdConfig, TimeKeeper}; 44 use whiskers_store::SqliteHousehold; 45 46 const DAY: u64 = 86_400_000; 47 48 #[test] 49 fn the_service_clears_its_copy_only_when_the_household_says_so_and_says_it_did() { 50 let store = Store::open_in_memory().unwrap(); 51 let hub = SqliteHouseholdHub::open(store.clone()); 52 let mut phone = SqliteLog::new(store.clone(), "phone"); 53 phone.append(&Entry::new(10 * DAY, Event::Heard { text: "old".into(), pictures: vec![] })).unwrap(); 54 phone.append(&Entry::new(99 * DAY, Event::Heard { text: "new".into(), pictures: vec![] })).unwrap(); 55 let now = 100 * DAY; 56 57 assert_eq!(apply_retention(&store, &hub, now), 0, "no setting keeps everything"); 58 59 let mut keeper = TimeKeeper::new(Box::new(SqliteHousehold::new(Store::open_in_memory().unwrap())), "phone"); 60 keeper.set_config(HouseholdConfig { log_retention_days: Some(30), ..HouseholdConfig::default() }, 5); 61 whiskers_ports::run_ready(hub.merge(keeper.household())).unwrap(); 62 assert_eq!(apply_retention(&store, &hub, now), 1); 63 assert_eq!(apply_retention(&store, &hub, now), 0, "once"); 64 let phone_lines = phone.own_entries().unwrap(); 65 assert_eq!(phone_lines[0].event, Event::Expired); 66 assert!(matches!(&phone_lines[1].event, Event::Heard { text, .. } if text == "new")); 67 let said = SqliteLog::new(store, SERVICE_DEVICE).own_entries().unwrap(); 68 assert!(matches!(said[0].event, Event::Cleared { lines: 1, .. }), "{said:?}"); 69 } 70}