use crate::crypto::checksum;
use crate::model::{Entry, Hlc, Manifest, ManifestEntry};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncBlob {
pub id: Uuid,
pub blob: String,
pub version: u64,
pub hlc: Hlc,
pub checksum: String,
}
impl SyncBlob {
pub fn from_raw(id: Uuid, raw: &[u8], version: u64, hlc: Hlc) -> Self {
Self {
id,
blob: STANDARD.encode(raw),
version,
hlc,
checksum: checksum(raw),
}
}
pub fn raw(&self) -> Result<Vec<u8>, base64::DecodeError> {
STANDARD.decode(&self.blob)
}
pub fn manifest_entry(&self) -> ManifestEntry {
ManifestEntry {
version: self.version,
hlc: self.hlc,
checksum: self.checksum.clone(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncRequest {
pub device_id: Uuid,
pub manifest: Manifest,
pub upload: Vec<SyncBlob>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SyncResponse {
pub download: Vec<SyncBlob>,
pub ack_uploaded: Vec<Uuid>,
pub server_manifest: Manifest,
}
pub fn dirty_entries<'a>(
entries: impl Iterator<Item = &'a Entry>,
manifest: &Manifest,
) -> Vec<&'a Entry> {
entries
.filter(|entry| match manifest.get(&entry.id) {
None => true,
Some(state) => entry.version != state.version || entry.hlc != state.hlc,
})
.collect()
}
pub fn manifest_entry_for_blob(blob: &[u8], entry: &Entry) -> ManifestEntry {
ManifestEntry {
version: entry.version,
hlc: entry.hlc,
checksum: checksum(blob),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{EntryKind, Hlc};
use chrono::Utc;
fn entry(version: u64) -> Entry {
Entry {
id: Uuid::now_v7(),
created_at: Utc::now(),
modified_at: Utc::now(),
hlc: Hlc {
wall_ms: version,
counter: 0,
node_id: 1,
},
body: "body".to_string(),
attachments: Vec::new(),
tags: vec![],
starred: false,
kind: EntryKind::Journal,
deleted: false,
version,
}
}
#[test]
fn dirty_checks_version_and_hlc() {
let entry = entry(2);
let mut manifest = Manifest::new();
manifest.insert(
entry.id,
ManifestEntry {
version: 1,
hlc: entry.hlc,
checksum: "x".to_string(),
},
);
assert_eq!(dirty_entries([&entry].into_iter(), &manifest), vec![&entry]);
}
}