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}