use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use kimun_core::{IndexObserver, NoteChange, nfs::VaultPath};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DirtyOp {
Upsert(u64),
Delete,
}
#[derive(Debug, Default)]
pub struct DirtySet {
inner: Mutex<HashMap<VaultPath, DirtyOp>>,
}
impl DirtySet {
pub fn record(&self, change: &NoteChange) {
let mut map = self.inner.lock().unwrap_or_else(|e| e.into_inner());
match change {
NoteChange::Upsert { path, hash } => map.insert(path.clone(), DirtyOp::Upsert(*hash)),
NoteChange::Delete { path } => map.insert(path.clone(), DirtyOp::Delete),
};
}
pub fn drain(&self) -> Vec<(VaultPath, DirtyOp)> {
let mut map = self.inner.lock().unwrap_or_else(|e| e.into_inner());
std::mem::take(&mut *map).into_iter().collect()
}
pub fn requeue(&self, items: impl IntoIterator<Item = (VaultPath, DirtyOp)>) {
let mut map = self.inner.lock().unwrap_or_else(|e| e.into_inner());
for (path, op) in items {
map.entry(path).or_insert(op);
}
}
pub fn len(&self) -> usize {
self.inner.lock().unwrap_or_else(|e| e.into_inner()).len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug)]
pub struct RagObserver {
dirty: Arc<DirtySet>,
}
impl RagObserver {
pub fn new(dirty: Arc<DirtySet>) -> Self {
Self { dirty }
}
}
impl IndexObserver for RagObserver {
fn on_change(&self, change: &NoteChange) {
self.dirty.record(change);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn upsert(p: &str) -> NoteChange {
NoteChange::Upsert {
path: VaultPath::new(p),
hash: 1,
}
}
fn delete(p: &str) -> NoteChange {
NoteChange::Delete {
path: VaultPath::new(p),
}
}
#[test]
fn latest_op_wins_per_path() {
let set = DirtySet::default();
set.record(&upsert("a.md"));
set.record(&delete("a.md")); let drained = set.drain();
assert_eq!(drained, vec![(VaultPath::new("a.md"), DirtyOp::Delete)]);
set.record(&delete("b.md"));
set.record(&upsert("b.md")); assert_eq!(
set.drain(),
vec![(VaultPath::new("b.md"), DirtyOp::Upsert(1))]
);
}
#[test]
fn drain_clears() {
let set = DirtySet::default();
set.record(&upsert("a.md"));
assert_eq!(set.len(), 1);
let _ = set.drain();
assert!(set.is_empty());
}
#[test]
fn requeue_does_not_clobber_newer_op() {
let set = DirtySet::default();
set.record(&delete("a.md"));
set.requeue([(VaultPath::new("a.md"), DirtyOp::Upsert(1))]);
assert_eq!(set.drain(), vec![(VaultPath::new("a.md"), DirtyOp::Delete)]);
}
#[test]
fn observer_records_into_shared_set() {
let dirty = Arc::new(DirtySet::default());
let observer = RagObserver::new(dirty.clone());
observer.on_change(&upsert("n.md"));
assert_eq!(dirty.len(), 1);
}
}