1#[cfg(not(target_family = "wasm"))] 2use std::fs; 3#[cfg(not(target_family = "wasm"))] 4use std::path::{Path, PathBuf}; 5 6#[cfg(not(target_family = "wasm"))] 7use ::log::{error, warn}; 8use ::log::{debug, info, trace}; 9 10use serde::{Deserialize, Serialize}; 11 12#[cfg(not(target_family = "wasm"))] 13use crate::ports::{Memory, MemoryError}; 14use crate::types::{Fact, MemorySnapshot}; 15use whiskers_icons::IconId; 16#[cfg(not(target_family = "wasm"))] 17use crate::types::Visibility; 18#[cfg(not(target_family = "wasm"))] 19use crate::types::NewFact; 20 21/// What Whiskers remembers, with no storage attached: the document every holder of the memory keeps, 22/// whether a file on a device, the hub on the service or a Durable Object. `next_id` is kept beside the 23/// facts so an id is never reused after the newest fact is forgotten. Its JSON is the memory file's. 24#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] 25pub struct MemoryDoc { 26 next_id: u64, 27 facts: Vec<Fact>, 28 /// Identities of facts the parents had forgotten, kept so another device cannot bring them back. 29 #[serde(default)] 30 forgotten: Vec<String>, 31} 32 33/// A fresh identity, random enough that two devices never pick the same one. 34#[cfg(not(target_family = "wasm"))] 35fn new_gid() -> String { 36 use std::hash::{BuildHasher, Hasher}; 37 trace!("minting a fact identity"); 38 let (a, b) = (std::collections::hash_map::RandomState::new(), std::collections::hash_map::RandomState::new()); 39 let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_nanos()); 40 let mut ha = a.build_hasher(); 41 ha.write_u128(now); 42 let mut hb = b.build_hasher(); 43 hb.write_u128(now.rotate_left(64)); 44 format!("{:016x}{:016x}", ha.finish(), hb.finish()) 45} 46 47/// Two facts that say the same thing, however it is spaced or capitalised, are one fact. 48fn same_words(text: &str) -> String { 49 text.split_whitespace().collect::<Vec<_>>().join(" ").to_lowercase() 50} 51 52impl MemoryDoc { 53 /// What this holder knows, for another to merge. 54 pub fn snapshot(&self) -> MemorySnapshot { 55 trace!("memory snapshot: {} facts, {} forgotten", self.facts.len(), self.forgotten.len()); 56 MemorySnapshot { facts: self.facts.clone(), forgotten: self.forgotten.clone() } 57 } 58 59 /// Takes in what another holder knows and returns how many facts changed here. Merging in any 60 /// order, any number of times, gives the same facts: what either side forgot stays forgotten, 61 /// the same thing learned twice is one fact, and an embedding computed on one side serves both. 62 /// Pure: it touches no storage, so each holder decides how to make the result durable. 63 pub fn merge(&mut self, remote: MemorySnapshot) -> usize { 64 debug!( 65 "memory merge begins: {} local facts, {} remote facts, {} remote forgotten", 66 self.facts.len(), remote.facts.len(), remote.forgotten.len() 67 ); 68 let mut changed = 0; 69 // What either side forgot stays forgotten, here and on the other side after its next merge. 70 for g in remote.forgotten { 71 if !self.forgotten.contains(&g) { 72 self.forgotten.push(g.clone()); 73 } 74 let n = self.facts.len(); 75 self.facts.retain(|f| f.gid != g); 76 changed += n - self.facts.len(); 77 } 78 for mut f in remote.facts { 79 if f.gid.is_empty() || self.forgotten.contains(&f.gid) { 80 trace!("merge skips a remote fact (no identity or forgotten here)"); 81 continue; 82 } 83 match self.facts.iter_mut().find(|l| l.gid == f.gid) { 84 Some(l) => { 85 // The same fact; an embedding computed on one side serves both. 86 if l.embedding.is_empty() && !f.embedding.is_empty() { 87 l.embedding = f.embedding; 88 changed += 1; 89 } 90 // A picture chosen on one side serves both; two choices meet at the smaller name, so every 91 // order agrees. 92 let icon = pick_icon(l.icon.clone(), f.icon.clone()); 93 if icon != l.icon { 94 l.icon = icon; 95 changed += 1; 96 } 97 // Whether she put it away is a register: the later write wins on every device. 98 let v = l.visibility.merged(f.visibility); 99 if v != l.visibility { 100 l.visibility = v; 101 changed += 1; 102 } 103 } 104 None => { 105 f.id = self.next_id.max(self.facts.iter().map(|x| x.id + 1).max().unwrap_or(0)).max(1); 106 self.next_id = f.id + 1; 107 self.facts.push(f); 108 changed += 1; 109 } 110 } 111 } 112 // The same thing learned on two devices arrives as two facts. The one with the smaller 113 // identity survives on every device, and the other is forgotten everywhere. 114 let mut seen: Vec<(String, String)> = Vec::new(); // (words, surviving gid) 115 let mut doomed: Vec<(String, String)> = Vec::new(); // (dropped gid, surviving gid) 116 let mut by_gid: Vec<&Fact> = self.facts.iter().collect(); 117 by_gid.sort_by(|a, b| a.gid.cmp(&b.gid)); 118 for f in by_gid { 119 let w = same_words(&f.text); 120 if let Some((_, survivor)) = seen.iter().find(|(x, _)| *x == w) { 121 doomed.push((f.gid.clone(), survivor.clone())); 122 } else { 123 seen.push((w, f.gid.clone())); 124 } 125 } 126 if !doomed.is_empty() { 127 info!("merge: {} duplicate fact(s) learned on two devices are being dropped", doomed.len()); 128 } 129 for (g, survivor) in doomed { 130 // What she did to the dropped copy is not lost: the survivor takes the later of the two. 131 let theirs = self.facts.iter().find(|f| f.gid == g).map(|f| (f.visibility, f.icon.clone())); 132 if let (Some((visibility, icon)), Some(s)) = (theirs, self.facts.iter_mut().find(|f| f.gid == survivor)) { 133 s.visibility = s.visibility.merged(visibility); 134 s.icon = pick_icon(s.icon.take(), icon); 135 } 136 self.facts.retain(|f| f.gid != g); 137 self.forgotten.push(g); 138 changed += 1; 139 } 140 info!("memory merge done: {changed} change(s); {} facts now", self.facts.len()); 141 changed 142 } 143} 144 145/// The picture two copies of a fact agree on: whichever has one, and the smaller name if both do. 146fn pick_icon(a: Option<IconId>, b: Option<IconId>) -> Option<IconId> { 147 match (a, b) { 148 (Some(a), Some(b)) => Some(a.min(b)), 149 (a, b) => a.or(b), 150 } 151} 152 153impl MemoryDoc { 154 /// Gives a fact its picture, if it has none yet. Returns the fact, or `None` if there is no such fact. 155 pub fn give_icon(&mut self, id: u64, icon: IconId) -> Option<Fact> { 156 let f = self.facts.iter_mut().find(|f| f.id == id)?; 157 if f.icon.is_none() { 158 f.icon = Some(icon); 159 } 160 Some(f.clone()) 161 } 162 163 /// She puts a fact away at `now_ms`. Returns the fact as it now stands, or `None` if there is no 164 /// such fact. Putting away what is already away changes nothing. 165 pub fn hide(&mut self, id: u64, now_ms: u64) -> Option<Fact> { 166 let f = self.facts.iter_mut().find(|f| f.id == id)?; 167 if !f.visibility.is_hidden() { 168 f.visibility = f.visibility.hidden_after(now_ms); 169 } 170 Some(f.clone()) 171 } 172 173 /// A parent restores a fact she put away. Restoring what is not away changes nothing. 174 pub fn restore(&mut self, id: u64, now_ms: u64) -> Option<Fact> { 175 let f = self.facts.iter_mut().find(|f| f.id == id)?; 176 if f.visibility.is_hidden() { 177 f.visibility = f.visibility.restored_after(now_ms); 178 } 179 Some(f.clone()) 180 } 181} 182 183/// What Whiskers remembers, as one JSON file replaced atomically on every change. 184#[cfg(not(target_family = "wasm"))] 185pub struct JsonMemory { 186 path: PathBuf, 187 saved: MemoryDoc, 188} 189 190#[cfg(not(target_family = "wasm"))] 191impl JsonMemory { 192 pub fn open(path: &Path) -> Result<Self, MemoryError> { 193 let saved = match fs::read_to_string(path) { 194 Ok(text) => serde_json::from_str(&text).map_err(|e| { 195 warn!("memory file {} does not parse: {e}", path.display()); 196 MemoryError(format!("memory file is damaged: {e}")) 197 })?, 198 Err(e) if e.kind() == std::io::ErrorKind::NotFound => { 199 debug!("no memory file at {}; starting empty", path.display()); 200 MemoryDoc::default() 201 } 202 Err(e) => { 203 error!("memory file {} unreadable: {e}", path.display()); 204 return Err(MemoryError(e.to_string())); 205 } 206 }; 207 debug!("memory opened: {} facts, {} forgotten", saved.facts.len(), saved.forgotten.len()); 208 Ok(Self { path: path.to_owned(), saved }) 209 } 210 211 /// Like [`open`](Self::open), but a file that cannot be read is moved aside (and returned) 212 /// and memory starts empty: the app must not be unable to start because of one bad file. 213 pub fn open_or_set_aside(path: &Path) -> Result<(Self, Option<PathBuf>), MemoryError> { 214 match Self::open(path) { 215 Ok(m) => Ok((m, None)), 216 Err(e) if e.0.contains("damaged") => { 217 warn!("memory file {} is damaged ({}); moving it aside and starting empty", path.display(), e.0); 218 let aside = crate::atomic::set_aside(path).map_err(|e| { 219 error!("could not move the damaged memory file aside: {e}"); 220 MemoryError(e.to_string()) 221 })?; 222 info!("damaged memory file kept as {}", aside.display()); 223 Ok((Self::open(path)?, Some(aside))) 224 } 225 Err(e) => { 226 error!("memory cannot be opened: {}", e.0); 227 Err(e) 228 } 229 } 230 } 231 232 /// Applies `f` to the document and keeps the result; the saved copy is put back if the save fails. 233 fn change(&mut self, id: u64, what: &str, f: impl FnOnce(&mut MemoryDoc) -> Option<Fact>) -> Result<Fact, MemoryError> { 234 let before = self.saved.clone(); 235 let Some(fact) = f(&mut self.saved) else { 236 warn!("{what}: no fact {id}"); 237 return Err(MemoryError(format!("no fact {id}"))); 238 }; 239 if self.saved != before 240 && let Err(e) = self.save() 241 { 242 error!("{what} of fact {id} not kept, the save failed: {}", e.0); 243 self.saved = before; 244 return Err(e); 245 } 246 info!("fact {id}: {what} (now hidden = {})", fact.visibility.is_hidden()); 247 Ok(fact) 248 } 249 250 fn save(&self) -> Result<(), MemoryError> { 251 let text = serde_json::to_string(&self.saved).map_err(|e| { 252 error!("memory does not serialize: {e}"); 253 MemoryError(e.to_string()) 254 })?; 255 trace!("saving memory: {} facts, {} bytes", self.saved.facts.len(), text.len()); 256 crate::atomic::write_atomic(&self.path, text.as_bytes()).map_err(|e| MemoryError(e.to_string())) 257 } 258} 259 260#[cfg(not(target_family = "wasm"))] 261impl Memory for JsonMemory { 262 fn facts(&self) -> Vec<Fact> { 263 trace!("memory: {} facts listed", self.saved.facts.len()); 264 self.saved.facts.clone() 265 } 266 267 fn add(&mut self, new: NewFact, at_ms: u64) -> Result<Fact, MemoryError> { 268 let id = self.saved.next_id.max(self.saved.facts.iter().map(|f| f.id + 1).max().unwrap_or(0)).max(1); 269 let fact = Fact { 270 id, 271 text: new.text, 272 learned_at_ms: at_ms, 273 kind: new.kind, 274 who: new.who, 275 place: new.place, 276 when: new.when, 277 pictures: new.pictures, 278 embedding: Vec::new(), 279 gid: new_gid(), 280 visibility: Visibility::default(), 281 icon: None, 282 }; 283 let before = self.saved.next_id; 284 self.saved.next_id = id + 1; 285 self.saved.facts.push(fact.clone()); 286 if let Err(e) = self.save() { 287 error!("fact {id} not kept, the save failed: {}", e.0); 288 self.saved.facts.pop(); 289 self.saved.next_id = before; 290 return Err(e); 291 } 292 info!("fact {id} added ({} chars); {} facts now", fact.text.len(), self.saved.facts.len()); 293 Ok(fact) 294 } 295 296 fn set_embedding(&mut self, id: u64, embedding: Vec<f32>) -> Result<(), MemoryError> { 297 let at = self.saved.facts.iter().position(|f| f.id == id).ok_or_else(|| { 298 warn!("set_embedding: no fact {id}"); 299 MemoryError(format!("no fact {id}")) 300 })?; 301 debug!("fact {id} gets an embedding of {} numbers", embedding.len()); 302 let before = std::mem::replace(&mut self.saved.facts[at].embedding, embedding); 303 if let Err(e) = self.save() { 304 error!("embedding for fact {id} not kept, the save failed: {}", e.0); 305 self.saved.facts[at].embedding = before; 306 return Err(e); 307 } 308 Ok(()) 309 } 310 311 fn forget(&mut self, id: u64) -> Result<Fact, MemoryError> { 312 let at = self.saved.facts.iter().position(|f| f.id == id).ok_or_else(|| { 313 warn!("forget: no fact {id}"); 314 MemoryError(format!("no fact {id}")) 315 })?; 316 let gone = self.saved.facts.remove(at); 317 let tombstoned = !gone.gid.is_empty() && !self.saved.forgotten.contains(&gone.gid); 318 if tombstoned { 319 self.saved.forgotten.push(gone.gid.clone()); 320 } 321 if let Err(e) = self.save() { 322 error!("forgetting fact {id} not kept, the save failed: {}", e.0); 323 self.saved.facts.insert(at, gone); 324 if tombstoned { 325 self.saved.forgotten.pop(); 326 } 327 return Err(e); 328 } 329 info!("fact {id} forgotten (tombstoned = {tombstoned}); {} facts left", self.saved.facts.len()); 330 Ok(gone) 331 } 332 333 fn give_icon(&mut self, id: u64, icon: IconId) -> Result<Fact, MemoryError> { 334 self.change(id, "give an icon to", |d| d.give_icon(id, icon)) 335 } 336 337 fn hide(&mut self, id: u64, at_ms: u64) -> Result<Fact, MemoryError> { 338 self.change(id, "hide", |d| d.hide(id, at_ms)) 339 } 340 341 fn restore(&mut self, id: u64, at_ms: u64) -> Result<Fact, MemoryError> { 342 self.change(id, "restore", |d| d.restore(id, at_ms)) 343 } 344 345 fn snapshot(&self) -> MemorySnapshot { 346 self.saved.snapshot() 347 } 348 349 fn merge(&mut self, remote: MemorySnapshot) -> Result<usize, MemoryError> { 350 let before = self.saved.clone(); 351 let changed = self.saved.merge(remote); 352 if (self.saved.facts != before.facts || self.saved.forgotten != before.forgotten) 353 && let Err(e) = self.save() 354 { 355 error!("memory merge not kept, the save failed: {}", e.0); 356 self.saved = before; 357 return Err(e); 358 } 359 Ok(changed) 360 } 361}