1use std::collections::HashMap;
13
14use serde::{Deserialize, Serialize};
15use time::OffsetDateTime;
16
17use crate::memo::{Memo, MemoHash, MemoId};
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ManifestRecord {
22 pub id: MemoId,
23 pub hash: MemoHash,
24 #[serde(with = "time::serde::rfc3339")]
25 pub updated_at: OffsetDateTime,
26 pub deleted: bool,
27}
28
29impl ManifestRecord {
30 pub fn from_memo(n: &Memo) -> Self {
31 Self {
32 id: n.id,
33 hash: n.hash.clone(),
34 updated_at: n.updated_at,
35 deleted: n.deleted_at.is_some(),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct FullRecord {
43 pub id: MemoId,
44 #[serde(with = "time::serde::rfc3339")]
45 pub created_at: OffsetDateTime,
46 #[serde(with = "time::serde::rfc3339")]
47 pub updated_at: OffsetDateTime,
48 pub hash: MemoHash,
49 pub favorite: bool,
50 pub category: String,
51 pub tags: Vec<String>,
52 pub body: String,
53 pub deleted: bool,
54}
55
56impl FullRecord {
57 pub fn from_memo(n: &Memo) -> Self {
58 Self {
59 id: n.id,
60 created_at: n.created_at,
61 updated_at: n.updated_at,
62 hash: n.hash.clone(),
63 favorite: n.favorite,
64 category: n.category.clone(),
65 tags: n.tags.clone(),
66 body: n.body.clone(),
67 deleted: n.deleted_at.is_some(),
68 }
69 }
70}
71
72#[derive(Debug, Clone, Default)]
74pub struct ManifestDiff {
75 pub to_fetch: Vec<MemoId>,
77 pub to_drop: Vec<MemoId>,
79 pub max_updated_at: Option<OffsetDateTime>,
81}
82
83pub fn diff_manifest(manifest: &[ManifestRecord], known: &HashMap<String, String>) -> ManifestDiff {
85 let mut diff = ManifestDiff::default();
86 for rec in manifest {
87 let key = rec.id.to_string();
88 if rec.deleted {
92 diff.to_drop.push(rec.id);
93 } else {
94 match known.get(&key) {
95 Some(h) if *h == rec.hash.0 => { }
96 _ => diff.to_fetch.push(rec.id),
97 }
98 }
99 diff.max_updated_at = Some(match diff.max_updated_at {
100 Some(t) if t >= rec.updated_at => t,
101 None => rec.updated_at,
102 Some(_) => rec.updated_at,
103 });
104 }
105 diff
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use std::collections::HashMap;
112
113 fn memo(body: &str, hash: &str) -> Memo {
114 let id = MemoId::now();
115 let now = OffsetDateTime::now_utc();
116 Memo {
117 id,
118 created_at: now,
119 updated_at: now,
120 hash: MemoHash::from_stored(hash),
121 favorite: false,
122 category: String::new(),
123 tags: vec![],
124 body: body.into(),
125 deleted_at: None,
126 }
127 }
128
129 #[test]
130 fn unchanged_skipped_changed_fetched() {
131 let a = memo("a", "b3:1");
132 let b = memo("b", "b3:2");
133 let manifest = vec![ManifestRecord::from_memo(&a), ManifestRecord::from_memo(&b)];
134 let mut known = HashMap::new();
135 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);
138 assert_eq!(diff.to_fetch, vec![b.id]);
139 assert!(diff.to_drop.is_empty());
140 }
141
142 #[test]
143 fn deleted_dropped() {
144 let mut n = memo("c", "b3:3");
145 n.deleted_at = Some(OffsetDateTime::now_utc());
146 let manifest = vec![ManifestRecord::from_memo(&n)];
147 let known = HashMap::from([(n.id.to_string(), "b3:3".to_string())]);
148 let diff = diff_manifest(&manifest, &known);
149 assert!(diff.to_fetch.is_empty());
150 assert_eq!(diff.to_drop, vec![n.id]);
151 }
152}