dittolive_ditto/store/attachment/
mod.rs

1//! Use attachments to sync large binary files between peers.
2
3use std::{
4    collections::HashMap,
5    path::{Path, PathBuf},
6    sync::{Arc, Weak},
7};
8
9use ffi_sdk::{BoxedAttachmentHandle, BoxedDitto};
10use safer_ffi::prelude::*;
11use serde::ser::SerializeMap;
12
13use crate::{ditto::TryUpgrade, prelude::DittoError, utils::prelude::ErrorKind};
14
15mod fetch_event;
16pub use self::fetch_event::DittoAttachmentFetchEvent;
17
18mod fetcher;
19pub use self::fetcher::{DittoAttachmentFetcher, FetcherVersion};
20
21mod token;
22pub use self::token::{DittoAttachmentToken, DittoAttachmentTokenLike};
23
24/// Represents an attachment and can be used to insert the associated attachment into a document at
25/// a specific key.
26#[derive(Debug)]
27pub struct DittoAttachment {
28    id: Box<[u8]>,
29    len: u64,
30    metadata: HashMap<String, String>,
31    ditto: Weak<BoxedDitto>,
32    attachment_handle: BoxedAttachmentHandle,
33}
34
35impl DittoAttachment {
36    /// Returns the `id` of this attachment, encoded so as to be compatible with DQL queries.
37    pub fn id(&self) -> String {
38        crate::utils::base64_encode_unpadded(&self.id)
39    }
40
41    /// Returns the `len`, in bytes, of this attachment's data.
42    #[allow(clippy::len_without_is_empty)]
43    pub fn len(&self) -> u64 {
44        self.len
45    }
46
47    /// Returns the metadata that was associated with this attachment file when the source peer
48    /// called [`new_attachment`][crate::store::Store::new_attachment].
49    pub fn metadata(&self) -> &HashMap<String, String> {
50        &self.metadata
51    }
52}
53
54impl serde::Serialize for DittoAttachment {
55    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
56    where
57        S: serde::Serializer,
58    {
59        let mut map = serializer.serialize_map(Some(4))?;
60        map.serialize_entry(
61            // This breaks compatibility with the Rust SDK prior
62            // to versions 3.0.8 and 4.0.0. It is necessary for
63            // cross-SDK attachments compatibility.
64            "_ditto_internal_type_jkb12973t4b",
65            &(::ffi_sdk::DittoCrdtType::Attachment as u64),
66        )?;
67        map.serialize_entry("_id", ::serde_bytes::Bytes::new(&self.id[..]))?;
68        map.serialize_entry("_len", &self.len)?;
69        map.serialize_entry("_meta", &self.metadata)?;
70        map.end()
71    }
72}
73
74impl DittoAttachment {
75    /// Create a new DittoAttachment
76    pub(crate) fn new(
77        id: Box<[u8]>,
78        len: u64,
79        metadata: HashMap<String, String>,
80        ditto: Weak<BoxedDitto>,
81        attachment_handle: BoxedAttachmentHandle,
82    ) -> Self {
83        Self {
84            id,
85            len,
86            metadata,
87            ditto,
88            attachment_handle,
89        }
90    }
91
92    pub(crate) fn from_file_and_metadata(
93        filepath: &(impl ?Sized + AsRef<Path>),
94        metadata: HashMap<String, String>,
95        ditto: &Arc<ffi_sdk::BoxedDitto>,
96    ) -> Result<DittoAttachment, DittoError> {
97        let source_path = char_p::new(filepath.as_ref().to_str().unwrap());
98        let file_operation = ffi_sdk::AttachmentFileOperation::Copy;
99        let mut slot = ::core::mem::MaybeUninit::<ffi_sdk::Attachment>::uninit();
100        let status = {
101            ffi_sdk::ditto_new_attachment_from_file(
102                ditto,
103                source_path.as_ref(),
104                file_operation,
105                slot.as_out(),
106            )
107        };
108        if status != 0 {
109            Err(DittoError::from_ffi(ErrorKind::InvalidInput))
110        } else {
111            let attachment = unsafe { slot.assume_init() }; // safe assuming above ffi call was successful
112            let ret = DittoAttachment::new(
113                attachment.id.into(),
114                attachment.len,
115                metadata,
116                Arc::downgrade(ditto),
117                attachment.handle,
118            );
119            Ok(ret)
120        }
121    }
122
123    pub(crate) fn from_bytes_and_metadata(
124        bytes: &(impl ?Sized + AsRef<[u8]>),
125        metadata: HashMap<String, String>,
126        ditto: &Arc<ffi_sdk::BoxedDitto>,
127    ) -> Result<DittoAttachment, DittoError> {
128        let mut slot = ::core::mem::MaybeUninit::<ffi_sdk::Attachment>::uninit();
129        let status = {
130            ffi_sdk::ditto_new_attachment_from_bytes(ditto, bytes.as_ref().into(), slot.as_out())
131        };
132        if status != 0 {
133            Err(DittoError::from_ffi(ErrorKind::InvalidInput))
134        } else {
135            let attachment = unsafe { slot.assume_init() }; // safe assuming above ffi call was successful
136            let ret = DittoAttachment::new(
137                attachment.id.into(),
138                attachment.len,
139                metadata,
140                Arc::downgrade(ditto),
141                attachment.handle,
142            );
143            Ok(ret)
144        }
145    }
146
147    /// Create a new DittoAttachment from a Token
148    pub(crate) fn new_with_token(
149        token: DittoAttachmentToken,
150        ditto: Weak<BoxedDitto>,
151        attachment_handle: BoxedAttachmentHandle,
152    ) -> Self {
153        Self {
154            id: token.id,
155            len: token.len,
156            metadata: token.metadata,
157            ditto,
158            attachment_handle,
159        }
160    }
161
162    /// Return path to an attachment.
163    /// # Panics
164    /// Panics if Ditto has been released
165    pub fn path(&self) -> PathBuf {
166        // FIXME(Ronan) Ideally wrap this function in a Result
167        let ditto = self.ditto.try_upgrade().unwrap();
168        let p = ffi_sdk::ditto_get_complete_attachment_path(&ditto, &self.attachment_handle);
169        let p_string = p.to_string();
170        p_string.into()
171    }
172}