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!();

pub mod document;
pub mod document_id;
pub mod pending_cursor_operation;
pub mod pending_id_specific_operation;
pub mod type_traits;

use std::mem::MaybeUninit;

use ::ffi_sdk::{Attachment, AttachmentFileOperation};
pub use document::*;
use pending_cursor_operation::*;
use pending_id_specific_operation::*;

use super::{
    ditto_attachment::DittoAttachment, ditto_attachment_fetch_event::DittoAttachmentFetchEvent,
    ditto_attachment_fetcher::DittoAttachmentFetcher, ditto_attachment_token::DittoAttachmentToken,
};
use crate::{
    ditto::WeakDittoHandleWrapper,
    error::{DittoError, ErrorKind},
};

#[derive(Clone, Debug)]
pub struct Collection {
    pub(super) ditto: WeakDittoHandleWrapper,
    pub(super) collection_name: char_p::Box,
}

impl Collection {
    pub(crate) fn new(ditto: WeakDittoHandleWrapper, collection_name: String) -> Self {
        let collection_name = char_p::new(collection_name.as_str());
        Self {
            ditto,
            collection_name,
        }
    }

    pub fn name(&self) -> &str {
        self.collection_name.as_ref().to_str()
    }

    /// Generates a DittoPendingCursorOperation that can be used to find all
    /// documents in the collection at a point in time or you can chain a call
    /// to observe_local or subscribe if you want to get updates about documents
    /// in the collection over time. It can also be used to update, remove, or
    /// evict documents.
    pub fn find_all(&self) -> PendingCursorOperation<'_> {
        self.find("true")
    }

    /// Generates a DittoPendingCursorOperation with the provided query that can
    /// be used to find the documents matching the query at a point in time or
    /// you can chain a call to observe_local or subscribe if you want to get
    /// updates about documents matching the query as they occur. It can also be
    /// used to update, remove, or evict documents.
    pub fn find(&self, query: &'_ str) -> PendingCursorOperation<'_> {
        PendingCursorOperation::<'_>::new(
            self.ditto.clone(),
            self.collection_name.to_owned(),
            query,
            None,
        )
    }

    /// Generates a DittoPendingCursorOperation with the provided query and
    /// query arguments that can be used to find the documents matching the
    /// query at a point in time or you can chain a call to observe_local or
    /// subscribe if you want to get updates about documents matching the query
    /// as they occur. It can also be used to update, remove, or evict
    /// documents.
    ///
    /// This is the recommended function to use when performing queries on a
    /// collection if you have any dynamic data included in the query string. It
    /// allows you to provide a query string with placeholders, in the form of
    /// `$args.my_arg_name`, along with an accompanying dictionary of arguments,
    /// in the form of `{ "my_arg_name": "some value" }`, and the placeholders
    /// will be appropriately replaced by the matching provided arguments from
    /// the dictionary. This includes handling things like wrapping strings in
    /// quotation marks and arrays in square brackets, for example.
    pub fn find_with_args<V: ::serde::Serialize, C: Borrow<V>>(
        &self,
        query: &'_ str,
        query_args: C,
    ) -> PendingCursorOperation<'_> {
        PendingCursorOperation::<'_>::new(
            self.ditto.clone(),
            self.collection_name.to_owned(),
            query,
            Some(serde_cbor::to_vec(query_args.borrow()).unwrap()),
        )
    }

    /// Generates a DittoPendingIDSpecificOperation with the provided document
    /// ID that can be used to find the document at a point in time or you can
    /// chain a call to observe_local or subscribe if you want to get updates
    /// about the document over time. It can also be used to update, remove, or
    /// evict the document.
    pub fn find_by_id(&self, doc_id: impl Into<DocumentId>) -> PendingIdSpecificOperation {
        PendingIdSpecificOperation {
            ditto: self.ditto.clone(),
            collection_name: self.collection_name.to_owned(),
            doc_id: doc_id.into(),
        }
    }
}

/// Convenience impl to avoid having to type `.clone()` everywhere.
impl From<&DocumentId> for DocumentId {
    fn from(doc: &DocumentId) -> Self {
        doc.clone()
    }
}

impl Collection {
    /// Inserts a new document into the collection and returns its ID. If the
    /// document already exists, the provided document content will be merged
    /// with the existing document's content.
    pub fn upsert<V: ::serde::Serialize, C: Borrow<V>>(
        &self,
        content: C,
    ) -> Result<DocumentId, DittoError> {
        self.insert_cbor(
            ::serde_cbor::to_vec(content.borrow()).unwrap().as_slice(),
            None,
            WriteStrategy::Merge,
            None,
        )
    }

    /// Inserts a new document into the collection and returns its ID. If the
    /// document already exists, the behavior is determined by the provided
    /// `write_strategy`.
    pub fn upsert_with_strategy<V: ::serde::Serialize, C: Borrow<V>>(
        &self,
        content: C,
        write_strategy: WriteStrategy,
    ) -> Result<DocumentId, DittoError> {
        self.insert_cbor(
            ::serde_cbor::to_vec(content.borrow()).unwrap().as_slice(),
            None,
            write_strategy,
            None,
        )
    }

    /// Inserts a new document into the collection and returns its assigned ID.
    /// Use this method when the content has already been serialized externally
    /// into a CBOR-formatted byte array
    pub(crate) fn insert_cbor(
        &self,
        cbor: &'_ [u8],
        id: Option<&DocumentId>,
        write_strategy: WriteStrategy,
        write_txn: Option<&'_ mut ffi_sdk::CWriteTransaction>,
    ) -> Result<DocumentId, DittoError> {
        let ditto = self
            .ditto
            .upgrade()
            .ok_or(crate::error::ErrorKind::ReleasedDittoInstance)?;
        let id: Option<c_slice::Ref<'_, u8>> = id.map(|id| id.as_ref().into());
        let write_strategy_rs = write_strategy.as_write_strategy_rs();

        let hint: Option<char_p::Ref<'_>> = None;
        let id = unsafe {
            ffi_sdk::ditto_collection_insert_value(
                &*ditto,
                self.collection_name.as_ref(),
                cbor.into(),
                id,
                write_strategy_rs,
                hint,
                write_txn,
            )
        }
        .ok_or(ErrorKind::InvalidInput)?;

        Ok(id.to::<Box<[u8]>>().into())
    }

    /// Creates a new attachment, which can then be inserted into a document.
    ///
    /// The file residing at the provided path will be copied into the
    /// Ditto’s store. The DittoAttachment object that is returned is what you
    /// can then use to insert an attachment into a document.
    ///
    /// You can provide metadata about the attachment, which will be replicated
    /// to other peers alongside the file attachment.
    ///
    /// Below is a snippet to show how you can use the new_attachment
    /// functionality to insert an attachment into a document.
    pub fn new_attachment<P: AsRef<Path>>(
        &self,
        path: P,
        metadata: HashMap<String, String>,
    ) -> Result<DittoAttachment, DittoError> {
        let ditto = self
            .ditto
            .upgrade()
            .ok_or(crate::error::ErrorKind::ReleasedDittoInstance)?;
        use ::safer_ffi::prelude::AsOut;
        let source_path = char_p::new(path.as_ref().to_str().unwrap());
        let file_operation = AttachmentFileOperation::Copy;
        let mut slot = MaybeUninit::<Attachment>::uninit();
        let out_attachment: Out<'_, Attachment> = slot.as_out();
        let status = unsafe {
            ffi_sdk::ditto_new_attachment_from_file(
                &ditto,
                source_path.as_ref(),
                file_operation,
                out_attachment,
            )
        };
        if status != 0 {
            Err(DittoError::from_ffi(ErrorKind::InvalidInput))
        } else {
            let attachment = unsafe { slot.assume_init() }; // safe assuming above ffi call was successful
            let ret = DittoAttachment::new(
                attachment.id.into(),
                attachment.len,
                metadata,
                self.ditto.clone(),
                attachment.handle,
            );
            Ok(ret)
        }
    }

    /// Fetch the attachment corresponding to the provided attachment token.
    /// * `onchange` - A closure that will be called when the status of the request to fetch the
    ///   attachment has
    /// changed. If the attachment is already available then this will be called
    /// almost immediately with a completed status value.
    pub fn fetch_attachment<'a>(
        &self,
        attachment_token: DittoAttachmentToken,
        on_fetch_event: impl Fn(DittoAttachmentFetchEvent) + Send + Sync + 'a,
    ) -> Result<DittoAttachmentFetcher<'a>, DittoError> {
        let ditto = self
            .ditto
            .upgrade()
            .ok_or(crate::error::ErrorKind::ReleasedDittoInstance)?;
        DittoAttachmentFetcher::new(attachment_token, ditto, on_fetch_event)
    }
}