use crate::note_queue::state_dir;
use crate::{atoms_lean, pack, NOTE_SOURCE_ENTITY};
use anyhow::{Context, Result};
use serde_json::Value;
use std::path::{Path, PathBuf};
pub fn from_ljos_note() -> String {
format!("{NOTE_SOURCE_ENTITY}ljos-note")
}
#[must_use]
pub fn written_by_note(atoms: &[Value]) -> Vec<String> {
let marker = from_ljos_note();
atoms
.iter()
.filter(|atom| {
atom["entities"]
.as_array()
.is_some_and(|tags| tags.iter().any(|tag| tag.as_str() == Some(&marker)))
})
.filter_map(|atom| atom["id"].as_str())
.filter(|id| !id.is_empty())
.map(str::to_string)
.collect()
}
#[must_use]
pub fn marker_path(dir: &Path) -> PathBuf {
dir.join("note-atoms-retired")
}
#[must_use]
pub fn swept(dir: &Path) -> bool {
marker_path(dir).exists()
}
pub fn mark_swept(dir: &Path, retired: usize) {
if std::fs::create_dir_all(dir).is_err() {
return;
}
let _ = std::fs::write(marker_path(dir), format!("{retired}\n"));
}
#[must_use]
pub fn line(retired: usize, left: usize) -> String {
let mut out = match retired {
0 => "retire-notes: no note memories are left to retire\n".to_string(),
1 => "retire-notes: retired 1 note memory from review\n".to_string(),
n => format!("retire-notes: retired {n} note memories from review\n"),
};
if left > 0 {
out.push_str(&format!(
"retire-notes: {left} the pack would not retire; run it again\n"
));
}
out.push_str("the tracker keeps every note; only the memory copies are gone\n");
out
}
pub fn sweep() -> Result<String> {
let dir = state_dir();
if swept(&dir) {
return Ok("retire-notes: this seat has swept already\n".to_string());
}
let client = pack()?;
let workspace = client.workspace();
let atoms = atoms_lean(&client, &workspace).context("retire-notes: GET /v1/atoms failed")?;
let ids = written_by_note(&atoms);
let mut retired = 0;
let mut left = 0;
for id in &ids {
if client.delete_atom(&workspace, id, None).is_ok() {
retired += 1;
} else {
left += 1;
}
}
if left == 0 {
mark_swept(&dir, retired);
}
Ok(line(retired, left))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn atom(id: &str, source: &str) -> Value {
json!({
"id": id,
"kind": "note",
"text": "the build needs the vendor dir",
"entities": ["seat:rg", format!("{NOTE_SOURCE_ENTITY}{source}")],
})
}
#[test]
fn the_notes_the_old_verb_wrote_are_the_ones_retired() {
let atoms = vec![
atom("a1", "ljos-note"),
atom("a2", "ljos-remember"),
atom("a3", "session-end-extract"),
atom("a4", "ljos-note"),
];
assert_eq!(written_by_note(&atoms), vec!["a1", "a4"]);
}
#[test]
fn a_lesson_is_not_a_note_and_is_left_alone() {
let atoms = vec![json!({
"id": "l1",
"kind": "lesson",
"text": "ask before rewriting history",
"entities": ["seat:rg", "horizon:standing"],
})];
assert!(written_by_note(&atoms).is_empty());
}
#[test]
fn the_marker_stops_a_second_sweep() {
let dir = tempfile::tempdir().unwrap();
assert!(!swept(dir.path()));
mark_swept(dir.path(), 12);
assert!(swept(dir.path()));
assert_eq!(
std::fs::read_to_string(marker_path(dir.path())).unwrap(),
"12\n"
);
}
#[test]
fn the_report_counts_what_it_retired() {
assert!(line(9513, 0).starts_with("retire-notes: retired 9513 note memories"));
assert!(line(1, 0).contains("retired 1 note memory"));
assert!(line(0, 0).contains("no note memories are left"));
assert!(line(4, 2).contains("2 the pack would not retire"));
}
}