dittolive-ditto 3.0.9

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_prelude!();
use std::{borrow::BorrowMut, time::SystemTime};

use ffi_sdk::PathAccessorType;
use serde::{de::DeserializeOwned, Serialize};

use super::type_traits::MutableValue;
use crate::error::{DittoError, ErrorKind};

// Similar to an ObjC Extension, DittoDocument can be implemented
// for a number of opaque types (usually various smart pointers)
pub trait DittoDocument {
    /// Returns the ID of the document
    fn id(&self) -> DocumentId;
    /// Renders the document as a CBOR value
    fn to_cbor(&self) -> Result<serde_cbor::Value, DittoError>;

    /// Returns the value at a given path in the document
    /// The parameter `V` represents the type expected at the given path.
    /// Note however that other peers may have changed this type and
    /// deserialization may fail. In this case provide a general type like
    /// `serde_cbor::Value`
    fn get<V: DeserializeOwned>(&self, path: &str) -> Result<V, DittoError>;

    fn get_rga(&self, path: &str) -> Result<DittoRga, DittoError>;

    /// Decodes a document into a user-provided type provided that type
    /// implements `serde::Deserialize`
    /// Note that this is a snapshot of the current state of the Document, which
    /// may be later modified by other peers
    fn typed<T: DeserializeOwned>(&self) -> Result<T, DittoError>;
}

pub trait DittoMutDocument: DittoDocument {
    /// Returns the value at a given path in the mutable document
    /// The parameter `V` represents the type expected at the given path.
    /// Note however that other peers may have changed this type and
    /// deserialization may fail.
    // FIXME(Daniel): does this apply in this case ??
    // In this case provide a general type like `serde_cbor::Value`
    fn get_mut<V: MutableValue>(&mut self, path: &str) -> Result<V, DittoError>;

    /// Assigns a some serializable value to the document at the given path *and
    /// removes any other information present at that path*
    /// If the path does not exist it will be created.
    fn set<V: Serialize>(&mut self, path: &str, val: V) -> Result<(), DittoError>;

    /// Sets the value at the provided path and marks it as the default,
    /// indicating other peers in the network are expected to overwrite it.
    fn set_as_default<V: Serialize>(&mut self, path: &str, val: V) -> Result<(), DittoError>;

    /// Remove the value from a document at a given path
    fn remove(&mut self, path: &str) -> Result<(), DittoError>;

    /// Increment (+/-) a field previously transformed into a Counter by
    /// `amount`.
    fn increment(&mut self, path: &str, amount: f64) -> Result<(), DittoError>;
}

// impl DittoDocument for ffi_sdk::DocumentRef { // a Box<Document>
impl<T: Borrow<ffi_sdk::Document>> DittoDocument for T {
    fn id(&self) -> DocumentId {
        let id_slice = unsafe { ffi_sdk::ditto_document_id(self.borrow()) };
        let id = id_slice.as_slice().to_vec().into();
        unsafe {
            ffi_sdk::ditto_c_bytes_free(id_slice);
        }
        id
    }

    fn to_cbor(&self) -> Result<serde_cbor::Value, DittoError> {
        let c_bytes = unsafe { ffi_sdk::ditto_document_cbor(self.borrow()) };
        let result = serde_cbor::from_slice(c_bytes.as_slice())
            .map_err(|e| DittoError::new(ErrorKind::InvalidInput, e));
        unsafe {
            ffi_sdk::ditto_c_bytes_free(c_bytes);
        }
        result
    }

    fn get<V: DeserializeOwned>(&self, path: &str) -> Result<V, DittoError> {
        get_internal(self.borrow(), path, PathAccessorType::Any)
    }

    fn get_rga(&self, path: &str) -> Result<DittoRga, DittoError> {
        get_internal(self.borrow(), path, PathAccessorType::Rga)
    }

    fn typed<V: DeserializeOwned>(&self) -> Result<V, DittoError> {
        let c_bytes = unsafe { ffi_sdk::ditto_document_cbor(self.borrow()) };
        let result = serde_cbor::from_slice::<V>(c_bytes.as_slice())
            .map_err(|e| DittoError::new(ErrorKind::InvalidInput, e));
        unsafe {
            ffi_sdk::ditto_c_bytes_free(c_bytes);
        }
        result
    }
}

fn get_internal<V: DeserializeOwned>(
    doc: &ffi_sdk::Document,
    path: &str,
    path_type: PathAccessorType,
) -> Result<V, DittoError> {
    let c_path = char_p::new(path);
    let c_val =
        unsafe { ffi_sdk::ditto_document_get_cbor_with_path_type(doc, c_path.as_ref(), path_type) };
    c_val
        .ok_or(ErrorKind::NonExtant)
        .and_then(|x| -> Result<V, _> {
            let result = serde_cbor::from_slice(x.as_slice())
                .map_err(|e| DittoError::new(ErrorKind::InvalidInput, e));
            unsafe {
                ffi_sdk::ditto_c_bytes_free(x);
            }
            result
        })
}

impl<T: BorrowMut<ffi_sdk::Document>> DittoMutDocument for T {
    /// Retrieve the mutable version of any ditto mutable explicit type
    fn get_mut<V: MutableValue>(&mut self, path: &str) -> Result<V> {
        let base_value: V::BaseType = self.get(path)?;
        let doc = self.borrow_mut();
        let c_key = char_p::new(path);
        V::mutable_version(base_value, doc, c_key)
    }

    fn set<V: Serialize>(&mut self, path: &str, val: V) -> Result<(), DittoError> {
        let c_str = char_p::new(path);
        let cbor: Vec<u8> =
            serde_cbor::to_vec(&val).map_err(|e| DittoError::new(ErrorKind::InvalidInput, e))?;
        let status = unsafe {
            ffi_sdk::ditto_document_set_cbor(
                self.borrow_mut(),
                c_str.as_ref(),
                cbor.as_slice().into(),
                true, // Make a path if not present
            )
        };
        if status != 0 {
            Err(DittoError::from_ffi(ErrorKind::InvalidInput))
        } else {
            Ok(())
        }
    }

    fn set_as_default<V: Serialize>(&mut self, path: &str, val: V) -> Result<(), DittoError> {
        let c_str = char_p::new(path);
        let cbor: Vec<u8> =
            serde_cbor::to_vec(&val).map_err(|e| DittoError::new(ErrorKind::InvalidInput, e))?;

        // This should be reasonably stable for this application, otherwise we can
        // upgrade to the chrono library
        // Note the underlying impl is platform specific
        let timestamp = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
            Ok(n) => n.as_secs(),
            Err(e) => return Err(DittoError::new(ErrorKind::Internal, e)),
        };
        let status = unsafe {
            ffi_sdk::ditto_document_set_cbor_with_timestamp(
                self.borrow_mut(),
                c_str.as_ref(),
                cbor.as_slice().into(),
                true, // Make a path if not present
                timestamp as u32,
            )
        };
        if status != 0 {
            Err(DittoError::from_ffi(ErrorKind::InvalidInput))
        } else {
            Ok(())
        }
    }

    fn remove(&mut self, path: &str) -> Result<(), DittoError> {
        let pointer = char_p::new(path);
        let status = unsafe { ffi_sdk::ditto_document_remove(self.borrow_mut(), pointer.as_ref()) };
        if status != 0 {
            Err(DittoError::from_ffi(ErrorKind::InvalidInput))
        } else {
            Ok(())
        }
    }

    fn increment(&mut self, path: &str, amount: f64) -> Result<(), DittoError> {
        let pointer = char_p::new(path);

        let status = unsafe {
            ffi_sdk::ditto_document_increment_counter(self.borrow_mut(), pointer.as_ref(), amount)
        };
        if status != 0 {
            Err(DittoError::from_ffi(ErrorKind::InvalidInput))
        } else {
            Ok(())
        }
    }
}