use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::memo::{Memo, MemoHash, MemoId};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifestRecord {
pub id: MemoId,
pub hash: MemoHash,
#[serde(with = "time::serde::rfc3339")]
pub updated_at: OffsetDateTime,
pub deleted: bool,
}
impl ManifestRecord {
pub fn from_memo(n: &Memo) -> Self {
Self {
id: n.id,
hash: n.hash.clone(),
updated_at: n.updated_at,
deleted: n.deleted_at.is_some(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FullRecord {
pub id: MemoId,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
pub updated_at: OffsetDateTime,
pub hash: MemoHash,
pub favorite: bool,
pub category: String,
pub tags: Vec<String>,
pub body: String,
pub deleted: bool,
}
impl FullRecord {
pub fn from_memo(n: &Memo) -> Self {
Self {
id: n.id,
created_at: n.created_at,
updated_at: n.updated_at,
hash: n.hash.clone(),
favorite: n.favorite,
category: n.category.clone(),
tags: n.tags.clone(),
body: n.body.clone(),
deleted: n.deleted_at.is_some(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ManifestDiff {
pub to_fetch: Vec<MemoId>,
pub to_drop: Vec<MemoId>,
pub max_updated_at: Option<OffsetDateTime>,
}
pub fn diff_manifest(manifest: &[ManifestRecord], known: &HashMap<String, String>) -> ManifestDiff {
let mut diff = ManifestDiff::default();
for rec in manifest {
let key = rec.id.to_string();
if rec.deleted {
diff.to_drop.push(rec.id);
} else {
match known.get(&key) {
Some(h) if *h == rec.hash.0 => { }
_ => diff.to_fetch.push(rec.id),
}
}
diff.max_updated_at = Some(match diff.max_updated_at {
Some(t) if t >= rec.updated_at => t,
None => rec.updated_at,
Some(_) => rec.updated_at,
});
}
diff
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn memo(body: &str, hash: &str) -> Memo {
let id = MemoId::now();
let now = OffsetDateTime::now_utc();
Memo {
id,
created_at: now,
updated_at: now,
hash: MemoHash::from_stored(hash),
favorite: false,
category: String::new(),
tags: vec![],
body: body.into(),
deleted_at: None,
}
}
#[test]
fn unchanged_skipped_changed_fetched() {
let a = memo("a", "b3:1");
let b = memo("b", "b3:2");
let manifest = vec![ManifestRecord::from_memo(&a), ManifestRecord::from_memo(&b)];
let mut known = HashMap::new();
known.insert(a.id.to_string(), "b3:1".to_string()); known.insert(b.id.to_string(), "b3:OLD".to_string()); let diff = diff_manifest(&manifest, &known);
assert_eq!(diff.to_fetch, vec![b.id]);
assert!(diff.to_drop.is_empty());
}
#[test]
fn deleted_dropped() {
let mut n = memo("c", "b3:3");
n.deleted_at = Some(OffsetDateTime::now_utc());
let manifest = vec![ManifestRecord::from_memo(&n)];
let known = HashMap::from([(n.id.to_string(), "b3:3".to_string())]);
let diff = diff_manifest(&manifest, &known);
assert!(diff.to_fetch.is_empty());
assert_eq!(diff.to_drop, vec![n.id]);
}
}