dittolive_ditto/store/attachment/
mod.rs1use 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
15#[doc(hidden)]
17pub mod fetch_event;
18pub use self::fetch_event::DittoAttachmentFetchEvent;
19
20#[doc(hidden)]
22pub mod fetcher;
23pub use self::fetcher::{DittoAttachmentFetcher, FetcherVersion};
24
25#[doc(hidden)]
27pub mod token;
28pub use self::token::{DittoAttachmentToken, DittoAttachmentTokenLike};
29
30#[derive(Debug)]
31pub struct DittoAttachment {
34 id: Box<[u8]>,
35 len: u64,
36 metadata: HashMap<String, String>,
37 ditto: Weak<BoxedDitto>,
38 attachment_handle: BoxedAttachmentHandle,
39}
40
41impl DittoAttachment {
42 pub fn id(&self) -> String {
45 crate::utils::base64_encode_unpadded(&self.id)
46 }
47
48 #[allow(clippy::len_without_is_empty)]
51 pub fn len(&self) -> u64 {
52 self.len
53 }
54
55 pub fn metadata(&self) -> &HashMap<String, String> {
58 &self.metadata
59 }
60}
61
62impl serde::Serialize for DittoAttachment {
63 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
64 where
65 S: serde::Serializer,
66 {
67 let mut map = serializer.serialize_map(Some(4))?;
68 map.serialize_entry(
69 "_ditto_internal_type_jkb12973t4b",
73 &(::ffi_sdk::DittoCrdtType::Attachment as u64),
74 )?;
75 map.serialize_entry("_id", ::serde_bytes::Bytes::new(&self.id[..]))?;
76 map.serialize_entry("_len", &self.len)?;
77 map.serialize_entry("_meta", &self.metadata)?;
78 map.end()
79 }
80}
81
82impl DittoAttachment {
83 pub(crate) fn new(
85 id: Box<[u8]>,
86 len: u64,
87 metadata: HashMap<String, String>,
88 ditto: Weak<BoxedDitto>,
89 attachment_handle: BoxedAttachmentHandle,
90 ) -> Self {
91 Self {
92 id,
93 len,
94 metadata,
95 ditto,
96 attachment_handle,
97 }
98 }
99
100 pub(crate) fn from_file_and_metadata(
101 filepath: &(impl ?Sized + AsRef<Path>),
102 metadata: HashMap<String, String>,
103 ditto: &Arc<ffi_sdk::BoxedDitto>,
104 ) -> Result<DittoAttachment, DittoError> {
105 let source_path = char_p::new(filepath.as_ref().to_str().unwrap());
106 let file_operation = ffi_sdk::AttachmentFileOperation::Copy;
107 let mut slot = ::core::mem::MaybeUninit::<ffi_sdk::Attachment>::uninit();
108 let status = {
109 ffi_sdk::ditto_new_attachment_from_file(
110 ditto,
111 source_path.as_ref(),
112 file_operation,
113 slot.as_out(),
114 )
115 };
116 if status != 0 {
117 Err(DittoError::from_ffi(ErrorKind::InvalidInput))
118 } else {
119 let attachment = unsafe { slot.assume_init() }; let ret = DittoAttachment::new(
121 attachment.id.into(),
122 attachment.len,
123 metadata,
124 Arc::downgrade(ditto),
125 attachment.handle,
126 );
127 Ok(ret)
128 }
129 }
130
131 pub(crate) fn from_bytes_and_metadata(
132 bytes: &(impl ?Sized + AsRef<[u8]>),
133 metadata: HashMap<String, String>,
134 ditto: &Arc<ffi_sdk::BoxedDitto>,
135 ) -> Result<DittoAttachment, DittoError> {
136 let mut slot = ::core::mem::MaybeUninit::<ffi_sdk::Attachment>::uninit();
137 let status = {
138 ffi_sdk::ditto_new_attachment_from_bytes(ditto, bytes.as_ref().into(), slot.as_out())
139 };
140 if status != 0 {
141 Err(DittoError::from_ffi(ErrorKind::InvalidInput))
142 } else {
143 let attachment = unsafe { slot.assume_init() }; let ret = DittoAttachment::new(
145 attachment.id.into(),
146 attachment.len,
147 metadata,
148 Arc::downgrade(ditto),
149 attachment.handle,
150 );
151 Ok(ret)
152 }
153 }
154
155 pub(crate) fn new_with_token(
157 token: DittoAttachmentToken,
158 ditto: Weak<BoxedDitto>,
159 attachment_handle: BoxedAttachmentHandle,
160 ) -> Self {
161 Self {
162 id: token.id,
163 len: token.len,
164 metadata: token.metadata,
165 ditto,
166 attachment_handle,
167 }
168 }
169
170 pub fn path(&self) -> PathBuf {
174 let ditto = self.ditto.try_upgrade().unwrap();
176 let p = ffi_sdk::ditto_get_complete_attachment_path(&ditto, &self.attachment_handle);
177 let p_string = p.to_string();
178 p_string.into()
179 }
180}
181
182#[cfg(test)]
183#[allow(deprecated)]
184mod tests {
186 use std::{
187 collections::HashMap,
188 sync::{
189 atomic::{AtomicBool, Ordering},
190 Arc, Mutex,
191 },
192 };
193
194 use serde_json::json;
195
196 use crate::{
197 prelude::*,
198 store::{
199 ditto_attachment_fetch_event::DittoAttachmentFetchEvent,
200 ditto_attachment_token::DittoAttachmentToken,
201 },
202 test_helpers::setup_ditto,
203 };
204
205 #[::tokio::test]
206 async fn attachment_serialize() {
207 let ditto = setup_ditto().unwrap();
208 let store = ditto.store();
209 let collection = store.collection("test").unwrap();
210
211 let original_test_file_path = "tests/data/attachment_file_1.txt";
212
213 let metadata = {
214 let mut m = HashMap::new();
215 m.insert("key_1".to_string(), "value_1".to_string());
216 m.insert("key_2".to_string(), "value_2".to_string());
217 m
218 };
219
220 let attachment = store
221 .new_attachment(original_test_file_path, metadata.clone())
222 .await
223 .expect("new_attachment");
224 let attachment_id = attachment.id.clone();
225 let attachment_len = attachment.len;
226 let attachment_file_path = attachment.path();
227 assert_ne!(
228 original_test_file_path,
229 attachment_file_path
230 .clone()
231 .into_os_string()
232 .into_string()
233 .unwrap()
234 );
235
236 let id = collection
240 .upsert(json!({ "hello": "again", "att": attachment }))
241 .unwrap();
242
243 {
244 let mut map = HashMap::new();
245 map.insert("hello", serde_cbor::value::to_value("again").unwrap());
246 map.insert("att", serde_cbor::value::to_value(&attachment).unwrap());
247 let _other = collection.upsert(map).unwrap();
248 }
249
250 let mut doc = collection.find_by_id(id).exec().unwrap();
251
252 let set = doc.set("att_two", &attachment);
254 assert!(set.is_ok());
255
256 let attachment_token = doc.get::<DittoAttachmentToken>("att").unwrap();
257 assert_eq!(attachment_token.id, attachment_id);
258 assert_eq!(attachment_token.len, attachment_len);
259 assert_eq!(attachment_token.metadata, metadata);
260
261 let attachment_token_two = doc.get::<DittoAttachmentToken>("att_two").unwrap();
262 assert_eq!(attachment_token.id, attachment_token_two.id);
263 assert_eq!(attachment_token.len, attachment_token_two.len);
264 assert_eq!(attachment_token.metadata, attachment_token_two.metadata);
265
266 let test_file = std::fs::read(original_test_file_path).unwrap();
267 let attachment_file = std::fs::read(attachment_file_path).unwrap();
268
269 assert_eq!(test_file, attachment_file);
270
271 assert_eq!(test_file.len() as u64, attachment_len);
272 }
273
274 #[test]
275 fn attachment_fetch_legacy() {
276 #![allow(deprecated)]
277 let ditto = setup_ditto().unwrap();
278 let store = ditto.store();
279 let collection = store.collection("test").unwrap();
280
281 let original_test_file_path = "tests/data/attachment_file_1.txt";
282
283 let attachment = collection
284 .new_attachment(original_test_file_path, HashMap::new())
285 .expect("new_attachment");
286
287 let collection = store.collection("test").unwrap();
288 let id = collection.upsert(json!({"hello": "again"})).unwrap();
289 let mut doc = collection.find_by_id(id).exec().unwrap();
290
291 let set = doc.set("att", attachment);
292 assert!(set.is_ok());
293
294 let attachment_token = doc.get::<DittoAttachmentToken>("att").unwrap();
295
296 let finished = Arc::new(AtomicBool::new(false));
297 let finished_clone = Arc::clone(&finished);
298 let fetched_attachment_data: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(vec![]));
299
300 let _fetcher = collection
301 .fetch_attachment(attachment_token, |event| {
302 if let DittoAttachmentFetchEvent::Completed { attachment } = event {
303 let att_data_mtx = &*fetched_attachment_data; if let Ok(mut fetched_attachment_data) = att_data_mtx.lock() {
305 *fetched_attachment_data = std::fs::read(attachment.path()).unwrap();
306 finished_clone.store(true, Ordering::SeqCst);
307 }
308 }
309 })
310 .unwrap();
311
312 while !finished.load(Ordering::SeqCst) {
313 std::thread::yield_now();
314 }
315
316 let test_file_data = std::fs::read(original_test_file_path).unwrap();
317 let fetched_att_data = fetched_attachment_data.lock().unwrap();
318 assert_eq!(test_file_data, *fetched_att_data);
319 }
320
321 #[::tokio::test]
322 async fn attachment_fetch() {
323 let ditto = setup_ditto().unwrap();
324 let store = ditto.store();
325 let collection = store.collection("test").unwrap();
326
327 let original_test_file_path = "tests/data/attachment_file_1.txt";
328
329 let attachment = store
330 .new_attachment(original_test_file_path, HashMap::new())
331 .await
332 .expect("new_attachment");
333
334 let id = collection.upsert(json!({"hello": "again"})).unwrap();
335 let mut doc = collection.find_by_id(id).exec().unwrap();
336
337 let set = doc.set("att", attachment);
338 assert!(set.is_ok());
339
340 let attachment_token = doc.get::<DittoAttachmentToken>("att").unwrap();
341
342 let finished = Arc::new(AtomicBool::new(false));
343 let finished_clone = Arc::clone(&finished);
344 let fetched_attachment_data: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(vec![]));
345
346 let _fetcher = store
347 .fetch_attachment(attachment_token, {
348 let fetched_attachment_data = fetched_attachment_data.clone();
349 move |event| {
350 if let DittoAttachmentFetchEvent::Completed { attachment } = event {
351 let att_data_mtx = &*fetched_attachment_data; if let Ok(mut fetched_attachment_data) = att_data_mtx.lock() {
353 *fetched_attachment_data = std::fs::read(attachment.path()).unwrap();
354 finished_clone.store(true, Ordering::SeqCst);
355 }
356 }
357 }
358 })
359 .unwrap();
360
361 while !finished.load(Ordering::SeqCst) {
362 std::thread::yield_now();
363 }
364
365 let test_file_data = std::fs::read(original_test_file_path).unwrap();
366 let fetched_att_data = fetched_attachment_data.lock().unwrap();
367 assert_eq!(test_file_data, *fetched_att_data);
368 }
369}