Skip to main content

note_to_self_lib/
sync.rs

1use crate::crypto::checksum;
2use crate::model::{Entry, Hlc, Manifest, ManifestEntry};
3use base64::engine::general_purpose::STANDARD;
4use base64::Engine;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9pub struct SyncBlob {
10    pub id: Uuid,
11    pub blob: String,
12    pub version: u64,
13    pub hlc: Hlc,
14    pub checksum: String,
15}
16
17impl SyncBlob {
18    pub fn from_raw(id: Uuid, raw: &[u8], version: u64, hlc: Hlc) -> Self {
19        Self {
20            id,
21            blob: STANDARD.encode(raw),
22            version,
23            hlc,
24            checksum: checksum(raw),
25        }
26    }
27
28    pub fn raw(&self) -> Result<Vec<u8>, base64::DecodeError> {
29        STANDARD.decode(&self.blob)
30    }
31
32    pub fn manifest_entry(&self) -> ManifestEntry {
33        ManifestEntry {
34            version: self.version,
35            hlc: self.hlc,
36            checksum: self.checksum.clone(),
37        }
38    }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42pub struct SyncRequest {
43    pub device_id: Uuid,
44    pub manifest: Manifest,
45    pub upload: Vec<SyncBlob>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
49pub struct SyncResponse {
50    pub download: Vec<SyncBlob>,
51    pub ack_uploaded: Vec<Uuid>,
52    pub server_manifest: Manifest,
53}
54
55pub fn dirty_entries<'a>(
56    entries: impl Iterator<Item = &'a Entry>,
57    manifest: &Manifest,
58) -> Vec<&'a Entry> {
59    entries
60        .filter(|entry| match manifest.get(&entry.id) {
61            None => true,
62            Some(state) => entry.version != state.version || entry.hlc != state.hlc,
63        })
64        .collect()
65}
66
67pub fn manifest_entry_for_blob(blob: &[u8], entry: &Entry) -> ManifestEntry {
68    ManifestEntry {
69        version: entry.version,
70        hlc: entry.hlc,
71        checksum: checksum(blob),
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::model::{EntryKind, Hlc};
79    use chrono::Utc;
80
81    fn entry(version: u64) -> Entry {
82        Entry {
83            id: Uuid::now_v7(),
84            created_at: Utc::now(),
85            modified_at: Utc::now(),
86            hlc: Hlc {
87                wall_ms: version,
88                counter: 0,
89                node_id: 1,
90            },
91            body: "body".to_string(),
92            attachments: Vec::new(),
93            tags: vec![],
94            starred: false,
95            kind: EntryKind::Journal,
96            deleted: false,
97            version,
98        }
99    }
100
101    #[test]
102    fn dirty_checks_version_and_hlc() {
103        let entry = entry(2);
104        let mut manifest = Manifest::new();
105        manifest.insert(
106            entry.id,
107            ManifestEntry {
108                version: 1,
109                hlc: entry.hlc,
110                checksum: "x".to_string(),
111            },
112        );
113        assert_eq!(dirty_entries([&entry].into_iter(), &manifest), vec![&entry]);
114    }
115}