dittolive-ditto 3.0.0-alpha2

Ditto is a peer to peer cross-platform database that allows mobile, web, IoT and server apps to sync with or without an internet connection.
Documentation
use std::{collections::HashMap, path::PathBuf, sync::Arc};

use ffi_sdk::{BoxedAttachmentHandle, BoxedDitto};
use serde::ser::SerializeMap;

use super::ditto_attachment_token::DittoAttachmentToken;

#[derive(Debug)]
pub struct DittoAttachment {
    id: Box<[u8]>,
    len: u64,
    metadata: HashMap<String, String>,
    ditto: Arc<BoxedDitto>,
    attachment_handle: BoxedAttachmentHandle,
}

impl serde::Serialize for DittoAttachment {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut map = serializer.serialize_map(Some(4))?;
        map.serialize_entry("_type", &(::ffi_sdk::DittoCrdtType::Attachment as u64))?;
        map.serialize_entry("_id", ::serde_bytes::Bytes::new(&self.id[..]))?;
        map.serialize_entry("_len", &self.len)?;
        map.serialize_entry("_meta", &self.metadata)?;
        map.end()
    }
}

impl DittoAttachment {
    pub fn new(
        id: Box<[u8]>,
        len: u64,
        metadata: HashMap<String, String>,
        ditto: Arc<BoxedDitto>,
        attachment_handle: BoxedAttachmentHandle,
    ) -> Self {
        Self {
            id,
            len,
            metadata,
            ditto,
            attachment_handle,
        }
    }

    pub fn new_with_token(
        token: DittoAttachmentToken,
        ditto: Arc<BoxedDitto>,
        attachment_handle: BoxedAttachmentHandle,
    ) -> Self {
        Self {
            id: token.id,
            len: token.len,
            metadata: token.metadata,
            ditto,
            attachment_handle,
        }
    }

    pub fn path(&self) -> PathBuf {
        let p = unsafe {
            ffi_sdk::ditto_get_complete_attachment_path(&self.ditto, &self.attachment_handle)
        };
        let p_string = p.to_string();
        p_string.into()
    }
}

#[cfg(test)]
mod tests {
    use serde_json::json;
    use std::collections::HashMap;

    use crate::{
        prelude::*,
        store::{
            ditto_attachment_fetch_event::DittoAttachmentFetchEvent,
            ditto_attachment_token::DittoAttachmentToken,
        },
        test_helpers::setup_ditto,
    };

    use std::sync::{
        atomic::{AtomicBool, Ordering},
        Arc, Mutex,
    };

    #[test]
    fn attachment_serialize() {
        let ditto = setup_ditto().unwrap();
        let store = ditto.store();
        let collection = store.collection("test").unwrap();

        let original_test_file_path = "tests/data/attachment_file_1.txt";

        let metadata = {
            let mut m = HashMap::new();
            m.insert("key_1".to_string(), "value_1".to_string());
            m.insert("key_2".to_string(), "value_2".to_string());
            m
        };

        let attachment = collection
            .new_attachment(original_test_file_path, metadata.clone())
            .expect("new_attachment");
        let attachment_id = attachment.id.clone();
        let attachment_len = attachment.len;
        let attachment_file_path = attachment.path();
        assert_ne!(
            original_test_file_path,
            attachment_file_path
                .clone()
                .into_os_string()
                .into_string()
                .unwrap()
        );

        let collection = store.collection("test").unwrap();
        let id = collection.upsert(json!({"hello": "again"})).unwrap();
        let mut doc = collection.find_by_id(id).exec().unwrap();

        let set = doc.set("att", attachment);
        assert!(set.is_ok());

        let attachment_token = doc.get::<DittoAttachmentToken>("att").unwrap();
        assert_eq!(attachment_token.id, attachment_id);
        assert_eq!(attachment_token.len, attachment_len);
        assert_eq!(attachment_token.metadata, metadata);

        let test_file = std::fs::read(original_test_file_path).unwrap();
        let attachment_file = std::fs::read(attachment_file_path).unwrap();

        assert_eq!(test_file, attachment_file);

        assert_eq!(test_file.len() as u64, attachment_len);
    }

    #[test]
    fn attachment_fetch() {
        let ditto = setup_ditto().unwrap();
        let store = ditto.store();
        let collection = store.collection("test").unwrap();

        let original_test_file_path = "tests/data/attachment_file_1.txt";

        let attachment = collection
            .new_attachment(original_test_file_path, HashMap::new())
            .expect("new_attachment");

        let collection = store.collection("test").unwrap();
        let id = collection.upsert(json!({"hello": "again"})).unwrap();
        let mut doc = collection.find_by_id(id).exec().unwrap();

        let set = doc.set("att", attachment);
        assert!(set.is_ok());

        let attachment_token = doc.get::<DittoAttachmentToken>("att").unwrap();

        let finished = Arc::new(AtomicBool::new(false));
        let finished_clone = Arc::clone(&finished);
        let fetched_attachment_data: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(vec![]));

        let _fetcher = collection
            .fetch_attachment(attachment_token, |event| {
                if let DittoAttachmentFetchEvent::Completed { attachment } = event {
                    let att_data_mtx = &*fetched_attachment_data; // move (copy) and reborrow
                    if let Ok(mut fetched_attachment_data) = att_data_mtx.lock() {
                        *fetched_attachment_data = std::fs::read(attachment.path()).unwrap();
                        finished_clone.store(true, Ordering::SeqCst);
                    }
                }
            })
            .unwrap();

        while !finished.load(Ordering::SeqCst) {
            std::thread::yield_now();
        }

        let test_file_data = std::fs::read(original_test_file_path).unwrap();
        let fetched_att_data = fetched_attachment_data.lock().unwrap();
        assert_eq!(test_file_data, *fetched_att_data);
    }
}