feat: local delete with seen marking for reader-side removal

This commit is contained in:
randogoth 2026-09-29 11:00:31 +03:00
parent 234b75651f
commit 1ae2752eb0

View file

@ -571,6 +571,23 @@ impl Store {
.is_some())
}
/// Removes one message from the local store — the reader's delete, which
/// the server-side DELETE (sec 6.1) knows nothing about. The id is marked
/// seen (sec 10), so a copy still sitting on the server is fetched again
/// without being re-stored: deleting locally must not resurrect the
/// message on the next fetch.
pub fn delete_local(&self, folder: &str, id: &[u8; ID_LEN]) -> Result<(), Error> {
let table = match folder {
"sent" => "sent",
_ => "inbox",
};
self.db()
.execute(&format!("DELETE FROM {table} WHERE id = ?1"), params![id])?;
self.db()
.execute("INSERT OR IGNORE INTO seen (id, at) VALUES (?1, ?2)", params![id, now()])?;
Ok(())
}
/// One folder, ordered by arrival or sending time. `folder` is "inbox",
/// "requests", "sent" or "all".
pub fn mail(&self, folder: &str) -> Result<Vec<Stored>, Error> {
@ -887,6 +904,24 @@ mod tests {
assert!(store.history("b@example.org").unwrap().is_empty());
}
#[test]
fn local_delete_marks_seen_so_a_refetch_does_not_restore() {
let store = temp_store("delete-local");
let id = [3u8; 32];
store.store_inbox(&id, b"envelope", 100, false, true).unwrap();
store.delete_local("inbox", &id).unwrap();
assert!(store.mail("all").unwrap().is_empty());
// Sec 10: the id is seen, so the fetch pipeline will not re-store
// the still-kept server copy. (The dedupe is the pipeline's seen
// check, not store_inbox's — a direct insert is a caller override.)
assert!(store.seen(&id).unwrap());
// Sent copies delete locally too, with the same protection.
let sent_id = [4u8; 32];
store.store_sent(&sent_id, "bob@example.org", b"sent", 50).unwrap();
store.delete_local("sent", &sent_id).unwrap();
assert!(store.mail("sent").unwrap().is_empty());
}
#[test]
fn in_memory_store_round_trips() {
let store = Store::open_in_memory().expect("memory store opens");