dittolive-ditto 3.0.8

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 ffi_sdk::{COrderByParam, FsComponent, WriteStrategyRs};

use crate::{
    disk_usage::DiskUsage,
    error::{DittoError, ErrorKind},
};

pub mod batch;
pub mod collection;
pub mod collections;
pub mod ditto_attachment;
pub mod ditto_attachment_fetch_event;
pub mod ditto_attachment_fetcher;
pub mod ditto_attachment_token;
pub mod live_query;
#[cfg(feature = "timeseries")]
pub mod timeseries;
pub mod update;

use collections::pending_collections_operation::PendingCollectionsOperation;

#[derive(Clone)]
pub struct Store {
    ditto: Arc<ffi_sdk::BoxedDitto>,
    disk_usage: Arc<DiskUsage>,
}

impl Store {
    pub fn new(ditto: Arc<ffi_sdk::BoxedDitto>) -> Self {
        let disk_usage = Arc::new(DiskUsage::new(ditto.retain(), FsComponent::Store));
        Self { ditto, disk_usage }
    }

    // Note this method's logic will be moved into the core ditto library
    // in the future
    fn validate_collection_name(name: &str) -> Result<(), DittoError> {
        let mut result = Ok(());

        if name.is_empty() {
            result = Err(DittoError::new(
                ErrorKind::InvalidInput,
                String::from("Collection name can not be empty"),
            ));
        }

        if name.split_whitespace().next().is_none() {
            result = Err(DittoError::new(
                ErrorKind::InvalidInput,
                String::from("Collection name can not only contain whitespace"),
            ));
        }

        result
    }

    /// Returns a `Collection` with the provided name.
    pub fn collection(&self, collection_name: &'_ str) -> Result<Collection, DittoError> {
        Self::validate_collection_name(collection_name)?;
        let c_name = char_p::new(collection_name);
        let status = unsafe { ffi_sdk::ditto_collection(&*self.ditto, c_name.as_ref()) };
        if status != 0 {
            return Err(DittoError::from_ffi(ErrorKind::InvalidInput));
        }
        Ok(Collection {
            ditto: Arc::downgrade(&self.ditto),
            collection_name: c_name,
        })
    }

    /// Returns an object that lets you fetch or observe the collections in the
    /// store.
    pub fn collections(&self) -> PendingCollectionsOperation<'_> {
        PendingCollectionsOperation::<'_>::new(Arc::downgrade(&self.ditto))
    }

    /// Allows you to group multiple operations together that affect multiple
    /// documents, potentially across multiple collections, without
    /// auto-committing on each operation.
    ///
    /// At the end of the batch of operations, either `batch.commit_changes()`
    /// or `batch.revert_changes()` must be called.
    ///
    /// ## Example
    ///
    /// ```rust
    /// # macro_rules! ignore {($($__:tt)*) => ()} ignore! {
    /// ditto.store().with_batched_write(|batch| {
    ///     let mut foo_coll = batch.collection("foo");
    ///     foo_coll.find...().remove();
    ///     let mut bar_coll = batch.collection("bar");
    ///     // Expensive multi-mutation op:
    ///     for _ in 0 .. 10_000 {
    ///         let doc = ...;
    ///         bar_coll.insert(doc, None, false);
    ///     }
    ///     // At this point, we must say whether we commit or revert
    ///     // these changes:
    ///     batch.commit_changes()
    /// })
    /// # }
    /// ```
    pub fn with_batched_write<F>(
        &self,
        f: F,
    ) -> Result<Vec<batch::WriteTransactionResult>, DittoError>
    where
        for<'batch> F: FnOnce(batch::ScopedStore<'batch>) -> batch::Action<'batch>,
    {
        batch::with_batched_write(self, f)
    }

    /// Returns a list of the names of collections in the store.
    // Is this just the local store or all collection in any peer
    pub fn collection_names(&self) -> Result<Vec<String>, DittoError> {
        let c_collections = unsafe { ffi_sdk::ditto_get_collection_names(&*self.ditto).ok()? };

        Ok(c_collections
            .iter()
            .map(|x: &char_p::Box| -> String { x.clone().into_string() })
            .collect())
    }

    /// Returns a hash representing the current version of the given queries.
    /// When a document matching such queries gets mutated, the hash will
    /// change as well.
    ///
    /// Please note that the hash depends on how queries are constructed, so you
    /// should make sure to always compare hashes generated with the same
    /// set of queries.
    pub fn queries_hash(&self, live_queries: &[LiveQuery]) -> Result<u64, DittoError> {
        let (coll_names, queries): (Vec<_>, Vec<_>) = live_queries
            .iter()
            .map(|lq| (lq.collection_name.as_ref(), lq.query.as_ref()))
            .unzip();

        unsafe {
            ffi_sdk::ditto_queries_hash(&self.ditto, coll_names[..].into(), queries[..].into()).ok()
        }
    }

    /// Returns a sequence of English words representing the current version of
    /// the given queries. When a document matching such queries gets
    /// mutated, the words will change as well.
    ///
    /// Please note that the resulting sequence of words depends on how queries
    /// are constructed, so you should make sure to always compare hashes
    /// generated with the same set of queries.
    pub fn queries_hash_mnemonic(&self, live_queries: &[LiveQuery]) -> Result<String, DittoError> {
        let (coll_names, queries): (Vec<_>, Vec<_>) = live_queries
            .iter()
            .map(|lq| (lq.collection_name.as_ref(), lq.query.as_ref()))
            .unzip();

        unsafe {
            ffi_sdk::ditto_queries_hash_mnemonic(
                &self.ditto,
                coll_names[..].into(),
                queries[..].into(),
            )
            .ok()
            .map(|c_str| c_str.unwrap().into_string())
        }
    }

    pub fn start_all_live_query_webhooks(&self) -> Result<(), DittoError> {
        unsafe {
            let ret = ffi_sdk::ditto_live_query_webhook_start_all(&self.ditto);
            if ret != 0 {
                return Err(DittoError::from_ffi(ErrorKind::Internal));
            }
        }
        Ok(())
    }

    pub fn start_live_query_webhook_by_id(&self, doc_id: DocumentId) -> Result<(), DittoError> {
        unsafe {
            let ret =
                ffi_sdk::ditto_live_query_webhook_start_by_id(&self.ditto, doc_id.bytes[..].into());
            if ret != 0 {
                return Err(DittoError::from_ffi(ErrorKind::Internal));
            }
        }
        Ok(())
    }

    pub fn register_live_query_webhook(
        &self,
        collection_name: &str,
        query: &str,
        url: &str,
    ) -> Result<DocumentId, DittoError> {
        let c_collection_name = char_p::new(collection_name);
        let c_query = char_p::new(query);
        let c_url = char_p::new(url);
        let order_definitions: Vec<COrderByParam<'_>> = Vec::with_capacity(0);
        let doc_id = unsafe {
            ffi_sdk::ditto_live_query_webhook_register_str(
                &self.ditto,
                c_collection_name.as_ref(),
                c_query.as_ref(),
                order_definitions[..].into(),
                -1,
                0,
                c_url.as_ref(),
            )
            .ok()?
            .to::<Box<[u8]>>()
            .into()
        };

        Ok(doc_id)
    }

    pub fn live_query_webhook_generate_new_api_secret(&self) -> Result<(), DittoError> {
        unsafe {
            let ret = ffi_sdk::ditto_live_query_webhook_generate_new_api_secret(&self.ditto);
            if ret != 0 {
                return Err(DittoError::from_ffi(ErrorKind::Internal));
            }
        }
        Ok(())
    }

    /// Returns a `Timeseries` with the provided name.
    pub fn timeseries(&self, ts_name: &'_ str) -> Result<TimeSeries, DittoError> {
        Self::validate_collection_name(ts_name)?;
        let c_name = char_p::new(ts_name);
        Ok(TimeSeries {
            ditto: self.ditto.retain(),
            ts_name: c_name,
        })
    }

    /// Return a `DiskUsage` to monitor the disk usage of the Store.
    pub fn disk_usage(&self) -> &DiskUsage {
        &self.disk_usage
    }
}

#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SortDirection {
    Ascending,
    Descending,
}

#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum WriteStrategy {
    Merge,
    InsertIfAbsent,
    InsertDefaultIfAbsent,
}

impl WriteStrategy {
    fn as_write_strategy_rs(&self) -> WriteStrategyRs {
        match self {
            WriteStrategy::Merge => WriteStrategyRs::Merge,
            WriteStrategy::InsertIfAbsent => WriteStrategyRs::InsertIfAbsent,
            WriteStrategy::InsertDefaultIfAbsent => WriteStrategyRs::InsertDefaultIfAbsent,
        }
    }
}