1use std::collections::BTreeMap;
2use std::io::Read;
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6use parking_lot::Mutex;
7use uuid::Uuid;
8
9use crate::crypto::{PublicKey, Secret, SecretError, SecretKey, Share};
10use crate::linked_data::{BlockEncoded, CodecError, Link};
11use crate::peer::{BlobsStore, BlobsStoreError};
12
13use super::manifest::Manifest;
14use super::node::{Node, NodeError, NodeLink};
15use super::pins::Pins;
16
17pub fn clean_path(path: &Path) -> PathBuf {
18 if !path.is_absolute() {
19 panic!("path is not absolute");
20 }
21 path.iter()
22 .skip(1)
23 .map(|part| part.to_string_lossy().to_string())
24 .collect::<PathBuf>()
25}
26
27#[derive(Clone)]
28pub struct MountInner {
29 pub link: Link,
31 pub manifest: Manifest,
33 pub entry: Node,
35 pub pins: Pins,
37}
38
39impl MountInner {
40 pub fn link(&self) -> &Link {
41 &self.link
42 }
43 pub fn entry(&self) -> &Node {
44 &self.entry
45 }
46 pub fn manifest(&self) -> &Manifest {
47 &self.manifest
48 }
49 pub fn pins(&self) -> &Pins {
50 &self.pins
51 }
52}
53
54#[derive(Clone)]
55pub struct Mount(Arc<Mutex<MountInner>>, BlobsStore);
56
57#[derive(Debug, thiserror::Error)]
58pub enum MountError {
59 #[error("default error: {0}")]
60 Default(#[from] anyhow::Error),
61 #[error("link not found")]
62 LinkNotFound(Link),
63 #[error("path not found: {0}")]
64 PathNotFound(PathBuf),
65 #[error("path is not a node: {0}")]
66 PathNotNode(PathBuf),
67 #[error("blobs store error: {0}")]
68 BlobsStore(#[from] BlobsStoreError),
69 #[error("secret error: {0}")]
70 Secret(#[from] SecretError),
71 #[error("node error: {0}")]
72 Node(#[from] NodeError),
73 #[error("codec error: {0}")]
74 Codec(#[from] CodecError),
75 #[error("share error: {0}")]
76 Share(#[from] crate::crypto::ShareError),
77 #[error("peers share was not found. this should be impossible")]
78 ShareNotFound,
79}
80
81impl Mount {
82 pub fn inner(&self) -> MountInner {
83 self.0.lock().clone()
84 }
85
86 pub fn blobs(&self) -> BlobsStore {
87 self.1.clone()
88 }
89
90 pub fn link(&self) -> Link {
91 let inner = self.0.lock();
92 inner.link.clone()
93 }
94
95 #[allow(clippy::await_holding_lock)]
97 pub async fn save(&self, blobs: &BlobsStore) -> Result<Link, MountError> {
98 let mut inner = self.0.lock();
99 let secret = Secret::generate();
101 let previous = inner.link.clone();
103 let entry = Self::_put_node_in_blobs(&inner.entry, &secret, blobs).await?;
105 inner.pins.insert(*entry.clone().hash());
108 inner.pins.insert(*previous.hash());
109 let pins_link = Self::_put_pins_in_blobs(&inner.pins, blobs).await?;
110 let mut manifest = inner.manifest.clone();
113 let _m = manifest.clone();
114 let shares = _m.shares();
115 manifest.unset_shares();
116 for share in shares.values() {
117 let public_key = share.principal().identity;
118 manifest.add_share(public_key, secret.clone())?;
119 }
120 manifest.set_pins(pins_link.clone());
122 manifest.set_previous(previous);
123 manifest.set_entry(entry.clone());
124 let link = Self::_put_manifest_in_blobs(&manifest, blobs).await?;
126
127 inner.manifest = manifest;
129
130 Ok(link)
131 }
132
133 pub async fn init(
134 id: Uuid,
135 name: String,
136 owner: &SecretKey,
137 blobs: &BlobsStore,
138 ) -> Result<Self, MountError> {
139 let entry = Node::default();
141 let secret = Secret::generate();
143 let entry_link = Self::_put_node_in_blobs(&entry, &secret, blobs).await?;
145 let share = Share::new(&secret, &owner.public())?;
147 let mut pins = Pins::new();
149 pins.insert(*entry_link.hash());
150 let pins_link = Self::_put_pins_in_blobs(&pins, blobs).await?;
152 let manifest = Manifest::new(
154 id,
155 name.clone(),
156 owner.public(),
157 share,
158 entry_link.clone(),
159 pins_link.clone(),
160 );
161 let link = Self::_put_manifest_in_blobs(&manifest, blobs).await?;
162
163 Ok(Mount(
165 Arc::new(Mutex::new(MountInner {
166 link,
167 manifest,
168 entry,
169 pins,
170 })),
171 blobs.clone(),
172 ))
173 }
174
175 pub async fn load(
176 link: &Link,
177 secret_key: &SecretKey,
178 blobs: &BlobsStore,
179 ) -> Result<Self, MountError> {
180 let public_key = &secret_key.public();
181 let manifest = Self::_get_manifest_from_blobs(link, blobs).await?;
182
183 let _share = manifest.get_share(public_key);
184
185 let share = match _share {
186 Some(share) => share.share(),
187 None => return Err(MountError::ShareNotFound),
188 };
189
190 let secret = share.recover(secret_key)?;
191
192 let pins = Self::_get_pins_from_blobs(manifest.pins(), blobs).await?;
193 let entry =
194 Self::_get_node_from_blobs(&NodeLink::Dir(manifest.entry().clone(), secret), blobs)
195 .await?;
196
197 Ok(Mount(
198 Arc::new(Mutex::new(MountInner {
199 link: link.clone(),
200 manifest,
201 entry,
202 pins,
203 })),
204 blobs.clone(),
205 ))
206 }
207
208 #[allow(clippy::await_holding_lock)]
209 pub async fn share(&mut self, peer: PublicKey) -> Result<(), MountError> {
210 let mut inner = self.0.lock();
211 inner.manifest.add_share(peer, Secret::default())?;
212 Ok(())
213 }
214
215 #[allow(clippy::await_holding_lock)]
216 pub async fn add<R>(
217 &mut self,
218 path: &Path,
219 data: R,
220 blobs: &BlobsStore,
221 ) -> Result<(), MountError>
222 where
223 R: Read + Send + Sync + 'static + Unpin,
224 {
225 let secret = Secret::generate();
226
227 let encrypted_reader = secret.encrypt_reader(data)?;
228
229 use bytes::Bytes;
231 use futures::stream;
232 let encrypted_bytes = {
233 let mut buf = Vec::new();
234 let mut reader = encrypted_reader;
235 reader.read_to_end(&mut buf).map_err(SecretError::Io)?;
236 buf
237 };
238
239 let stream = Box::pin(stream::once(async move {
240 Ok::<_, std::io::Error>(Bytes::from(encrypted_bytes))
241 }));
242
243 let hash = blobs.put_stream(stream).await?;
244
245 let link = Link::new(
246 crate::linked_data::LD_RAW_CODEC,
247 hash,
248 iroh_blobs::BlobFormat::Raw,
249 );
250
251 let node_link = NodeLink::new_data_from_path(link.clone(), secret, path);
252
253 let mut inner = self.0.lock();
254 let root_node = inner.entry.clone();
255 let (updated_link, node_hashes) =
256 Self::_set_node_link_at_path(root_node, node_link, path, blobs).await?;
257
258 inner.pins.insert(hash);
260 inner.pins.extend(node_hashes);
261
262 if let NodeLink::Dir(new_root_link, new_secret) = updated_link {
263 inner.entry = Self::_get_node_from_blobs(
264 &NodeLink::Dir(new_root_link.clone(), new_secret),
265 blobs,
266 )
267 .await?;
268 }
269
270 Ok(())
271 }
272
273 #[allow(clippy::await_holding_lock)]
274 pub async fn rm(&mut self, path: &Path, blobs: &BlobsStore) -> Result<(), MountError> {
275 let path = clean_path(path);
276 let parent_path = path
277 .parent()
278 .ok_or_else(|| MountError::Default(anyhow::anyhow!("Cannot remove root")))?;
279
280 let inner = self.0.lock();
281 let entry = inner.entry.clone();
282 drop(inner);
283
284 let mut parent_node = if parent_path == Path::new("") {
285 entry.clone()
286 } else {
287 Self::_get_node_at_path(&entry, parent_path, blobs).await?
288 };
289
290 let file_name = path.file_name().unwrap().to_string_lossy().to_string();
291
292 if parent_node.del(&file_name).is_none() {
293 return Err(MountError::PathNotFound(path.to_path_buf()));
294 }
295
296 if parent_path == Path::new("") {
297 let secret = Secret::generate();
298 let link = Self::_put_node_in_blobs(&parent_node, &secret, blobs).await?;
299
300 let mut inner = self.0.lock();
301 inner.pins.insert(*link.hash());
303 inner.entry = parent_node;
304 } else {
305 let secret = Secret::generate();
307 let parent_link = Self::_put_node_in_blobs(&parent_node, &secret, blobs).await?;
308 let node_link = NodeLink::new_dir(parent_link.clone(), secret);
309
310 let mut inner = self.0.lock();
311 let abs_parent_path = Path::new("/").join(parent_path);
313 let (updated_link, node_hashes) =
314 Self::_set_node_link_at_path(entry, node_link, &abs_parent_path, blobs).await?;
315
316 inner.pins.insert(*parent_link.hash());
318 inner.pins.extend(node_hashes);
319
320 if let NodeLink::Dir(new_root_link, new_secret) = updated_link {
321 inner.entry = Self::_get_node_from_blobs(
322 &NodeLink::Dir(new_root_link.clone(), new_secret),
323 blobs,
324 )
325 .await?;
326 }
327 }
328
329 Ok(())
330 }
331
332 #[allow(clippy::await_holding_lock)]
333 pub async fn ls(
334 &self,
335 path: &Path,
336 blobs: &BlobsStore,
337 ) -> Result<BTreeMap<PathBuf, NodeLink>, MountError> {
338 let mut items = BTreeMap::new();
339 let path = clean_path(path);
340
341 let inner = self.0.lock();
342 let root_node = inner.entry.clone();
343 drop(inner);
344
345 let node = if path == Path::new("") {
346 root_node
347 } else {
348 match Self::_get_node_at_path(&root_node, &path, blobs).await {
349 Ok(node) => node,
350 Err(MountError::LinkNotFound(_)) => {
351 return Err(MountError::PathNotNode(path.to_path_buf()))
352 }
353 Err(err) => return Err(err),
354 }
355 };
356
357 for (name, link) in node.get_links() {
358 let mut full_path = path.clone();
359 full_path.push(name);
360 items.insert(full_path, link.clone());
361 }
362
363 Ok(items)
364 }
365
366 pub async fn ls_deep(
367 &self,
368 path: &Path,
369 blobs: &BlobsStore,
370 ) -> Result<BTreeMap<PathBuf, NodeLink>, MountError> {
371 let base_path = clean_path(path);
372 self._ls_deep(path, &base_path, blobs).await
373 }
374
375 async fn _ls_deep(
376 &self,
377 path: &Path,
378 base_path: &Path,
379 blobs: &BlobsStore,
380 ) -> Result<BTreeMap<PathBuf, NodeLink>, MountError> {
381 let mut all_items = BTreeMap::new();
382
383 let items = self.ls(path, blobs).await?;
385
386 for (item_path, link) in items {
387 let relative_path = if base_path == Path::new("") {
389 item_path.clone()
390 } else {
391 item_path
392 .strip_prefix(base_path)
393 .unwrap_or(&item_path)
394 .to_path_buf()
395 };
396 all_items.insert(relative_path.clone(), link.clone());
397
398 if link.is_dir() {
399 let abs_item_path = Path::new("/").join(&item_path);
401 let sub_items = Box::pin(self._ls_deep(&abs_item_path, base_path, blobs)).await?;
402
403 for (sub_path, sub_link) in sub_items {
405 all_items.insert(sub_path, sub_link);
406 }
407 }
408 }
409
410 Ok(all_items)
411 }
412
413 #[allow(clippy::await_holding_lock)]
414 pub async fn cat(&self, path: &Path, blobs: &BlobsStore) -> Result<Vec<u8>, MountError> {
415 let path = clean_path(path);
416
417 let inner = self.0.lock();
418 let root_node = inner.entry.clone();
419 drop(inner);
420
421 let (parent_path, file_name) = if let Some(parent) = path.parent() {
422 (
423 parent,
424 path.file_name().unwrap().to_string_lossy().to_string(),
425 )
426 } else {
427 return Err(MountError::PathNotFound(path.to_path_buf()));
428 };
429
430 let parent_node = if parent_path == Path::new("") {
431 root_node
432 } else {
433 Self::_get_node_at_path(&root_node, parent_path, blobs).await?
434 };
435
436 let link = parent_node
437 .get_link(&file_name)
438 .ok_or_else(|| MountError::PathNotFound(path.to_path_buf()))?;
439
440 match link {
441 NodeLink::Data(link, secret, _) => {
442 let encrypted_data = blobs.get(link.hash()).await?;
443 let data = secret.decrypt(&encrypted_data)?;
444 Ok(data)
445 }
446 NodeLink::Dir(_, _) => Err(MountError::PathNotNode(path.to_path_buf())),
447 }
448 }
449
450 #[allow(clippy::await_holding_lock)]
452 pub async fn get(&self, path: &Path, blobs: &BlobsStore) -> Result<NodeLink, MountError> {
453 let path = clean_path(path);
454
455 let inner = self.0.lock();
456 let root_node = inner.entry.clone();
457 drop(inner);
458
459 let (parent_path, file_name) = if let Some(parent) = path.parent() {
460 (
461 parent,
462 path.file_name().unwrap().to_string_lossy().to_string(),
463 )
464 } else {
465 return Err(MountError::PathNotFound(path.to_path_buf()));
466 };
467
468 let parent_node = if parent_path == Path::new("") {
469 root_node
470 } else {
471 Self::_get_node_at_path(&root_node, parent_path, blobs).await?
472 };
473
474 parent_node
475 .get_link(&file_name)
476 .cloned()
477 .ok_or_else(|| MountError::PathNotFound(path.to_path_buf()))
478 }
479
480 async fn _get_node_at_path(
481 node: &Node,
482 path: &Path,
483 blobs: &BlobsStore,
484 ) -> Result<Node, MountError> {
485 let mut current_node = node.clone();
486 let mut consumed_path = PathBuf::from("/");
487
488 for part in path.iter() {
489 consumed_path.push(part);
490 let next = part.to_string_lossy().to_string();
491 let next_link = current_node
492 .get_link(&next)
493 .ok_or(MountError::PathNotFound(consumed_path.clone()))?;
494 current_node = Self::_get_node_from_blobs(next_link, blobs).await?
495 }
496 Ok(current_node)
497 }
498
499 pub async fn _set_node_link_at_path(
500 node: Node,
501 node_link: NodeLink,
502 path: &Path,
503 blobs: &BlobsStore,
504 ) -> Result<(NodeLink, Vec<crate::linked_data::Hash>), MountError> {
505 let path = clean_path(path);
506 let mut visited_nodes = Vec::new();
507 let mut name = path.file_name().unwrap().to_string_lossy().to_string();
508 let parent_path = path.parent().unwrap_or(Path::new(""));
509
510 let mut consumed_path = PathBuf::from("/");
511 let mut node = node;
512 visited_nodes.push((consumed_path.clone(), node.clone()));
513
514 for part in parent_path.iter() {
515 let next = part.to_string_lossy().to_string();
516 let next_link = node.get_link(&next);
517 if let Some(next_link) = next_link {
518 consumed_path.push(part);
519 match next_link {
520 NodeLink::Dir(..) => {
521 node = Self::_get_node_from_blobs(next_link, blobs).await?
522 }
523 NodeLink::Data(..) => {
524 return Err(MountError::PathNotNode(consumed_path.clone()));
525 }
526 }
527 visited_nodes.push((consumed_path.clone(), node.clone()));
528 } else {
529 node = Node::default();
531 consumed_path.push(part);
532 visited_nodes.push((consumed_path.clone(), node.clone()));
533 }
534 }
535
536 let mut node_link = node_link;
537 let mut created_hashes = Vec::new();
538 for (path, mut node) in visited_nodes.into_iter().rev() {
539 node.insert(name, node_link.clone());
540 let secret = Secret::generate();
541 let link = Self::_put_node_in_blobs(&node, &secret, blobs).await?;
542 created_hashes.push(*link.hash());
543 node_link = NodeLink::Dir(link, secret);
544 name = path
545 .file_name()
546 .unwrap_or_default()
547 .to_string_lossy()
548 .to_string();
549 }
550
551 Ok((node_link, created_hashes))
552 }
553
554 async fn _get_manifest_from_blobs(
555 link: &Link,
556 blobs: &BlobsStore,
557 ) -> Result<Manifest, MountError> {
558 tracing::debug!(
559 "_get_bucket_from_blobs: Checking for bucket data at link {:?}",
560 link
561 );
562 let hash = link.hash();
563 tracing::debug!("_get_bucket_from_blobs: Bucket hash: {}", hash);
564
565 match blobs.stat(hash).await {
566 Ok(true) => {
567 tracing::debug!(
568 "_get_bucket_from_blobs: Bucket hash {} exists in blobs",
569 hash
570 );
571 }
572 Ok(false) => {
573 tracing::error!("_get_bucket_from_blobs: Bucket hash {} NOT FOUND in blobs - LinkNotFound error!", hash);
574 return Err(MountError::LinkNotFound(link.clone()));
575 }
576 Err(e) => {
577 tracing::error!(
578 "_get_bucket_from_blobs: Error checking bucket hash {}: {}",
579 hash,
580 e
581 );
582 return Err(e.into());
583 }
584 }
585
586 tracing::debug!("_get_bucket_from_blobs: Reading bucket data from blobs");
587 let data = blobs.get(hash).await?;
588 tracing::debug!(
589 "_get_bucket_from_blobs: Got {} bytes of bucket data",
590 data.len()
591 );
592
593 let bucket_data = Manifest::decode(&data)?;
594 tracing::debug!(
595 "_get_bucket_from_blobs: Successfully decoded BucketData for bucket '{}'",
596 bucket_data.name()
597 );
598
599 Ok(bucket_data)
600 }
601
602 async fn _get_pins_from_blobs(link: &Link, blobs: &BlobsStore) -> Result<Pins, MountError> {
603 tracing::debug!("_get_pins_from_blobs: Checking for pins at link {:?}", link);
604 let hash = link.hash();
605 tracing::debug!("_get_pins_from_blobs: Pins hash: {}", hash);
606
607 match blobs.stat(hash).await {
608 Ok(true) => {
609 tracing::debug!("_get_pins_from_blobs: Pins hash {} exists in blobs", hash);
610 }
611 Ok(false) => {
612 tracing::error!(
613 "_get_pins_from_blobs: Pins hash {} NOT FOUND in blobs - LinkNotFound error!",
614 hash
615 );
616 return Err(MountError::LinkNotFound(link.clone()));
617 }
618 Err(e) => {
619 tracing::error!(
620 "_get_pins_from_blobs: Error checking pins hash {}: {}",
621 hash,
622 e
623 );
624 return Err(e.into());
625 }
626 }
627
628 tracing::debug!("_get_pins_from_blobs: Reading hash list from blobs");
629 let hashes = blobs.read_hash_list(*hash).await?;
631 tracing::debug!(
632 "_get_pins_from_blobs: Successfully read {} hashes from pinset",
633 hashes.len()
634 );
635
636 Ok(Pins::from_vec(hashes))
637 }
638
639 async fn _get_node_from_blobs(
640 node_link: &NodeLink,
641 blobs: &BlobsStore,
642 ) -> Result<Node, MountError> {
643 let link = node_link.link();
644 let secret = node_link.secret();
645 let hash = link.hash();
646
647 tracing::debug!("_get_node_from_blobs: Checking for node at hash {}", hash);
648
649 match blobs.stat(hash).await {
650 Ok(true) => {
651 tracing::debug!("_get_node_from_blobs: Node hash {} exists in blobs", hash);
652 }
653 Ok(false) => {
654 tracing::error!(
655 "_get_node_from_blobs: Node hash {} NOT FOUND in blobs - LinkNotFound error!",
656 hash
657 );
658 return Err(MountError::LinkNotFound(link.clone()));
659 }
660 Err(e) => {
661 tracing::error!(
662 "_get_node_from_blobs: Error checking node hash {}: {}",
663 hash,
664 e
665 );
666 return Err(e.into());
667 }
668 }
669
670 tracing::debug!("_get_node_from_blobs: Reading encrypted node blob");
671 let blob = blobs.get(hash).await?;
672 tracing::debug!(
673 "_get_node_from_blobs: Got {} bytes of encrypted node data",
674 blob.len()
675 );
676
677 tracing::debug!("_get_node_from_blobs: Decrypting node data");
678 let data = secret.decrypt(&blob)?;
679 tracing::debug!("_get_node_from_blobs: Decrypted {} bytes", data.len());
680
681 let node = Node::decode(&data)?;
682 tracing::debug!("_get_node_from_blobs: Successfully decoded Node");
683
684 Ok(node)
685 }
686
687 async fn _put_node_in_blobs(
691 node: &Node,
692 secret: &Secret,
693 blobs: &BlobsStore,
694 ) -> Result<Link, MountError> {
695 let _data = node.encode()?;
696 let data = secret.encrypt(&_data)?;
697 let hash = blobs.put(data).await?;
698 let link = Link::new(
701 crate::linked_data::LD_RAW_CODEC,
702 hash,
703 iroh_blobs::BlobFormat::Raw,
704 );
705 Ok(link)
706 }
707
708 pub async fn _put_manifest_in_blobs(
709 bucket_data: &Manifest,
710 blobs: &BlobsStore,
711 ) -> Result<Link, MountError> {
712 let data = bucket_data.encode()?;
713 let hash = blobs.put(data).await?;
714 let link = Link::new(bucket_data.codec(), hash, iroh_blobs::BlobFormat::Raw);
717 Ok(link)
718 }
719
720 pub async fn _put_pins_in_blobs(pins: &Pins, blobs: &BlobsStore) -> Result<Link, MountError> {
721 let hash = blobs.create_hash_list(pins.iter().copied()).await?;
723 let link = Link::new(
725 crate::linked_data::LD_RAW_CODEC,
726 hash,
727 iroh_blobs::BlobFormat::HashSeq,
728 );
729 Ok(link)
730 }
731}
732
733#[cfg(test)]
734mod test {
735 use super::*;
736 use std::io::Cursor;
737 use tempfile::TempDir;
738
739 async fn setup_test_env() -> (Mount, BlobsStore, crate::crypto::SecretKey, TempDir) {
740 let temp_dir = TempDir::new().unwrap();
741 let blob_path = temp_dir.path().join("blobs");
742
743 let secret_key = SecretKey::generate();
744 let blobs = BlobsStore::load(&blob_path).await.unwrap();
746
747 let mount = Mount::init(Uuid::new_v4(), "test".to_string(), &secret_key, &blobs)
748 .await
749 .unwrap();
750
751 (mount, blobs, secret_key, temp_dir)
752 }
753
754 #[tokio::test]
755 async fn test_add_and_cat() {
756 let (mut mount, blobs, _, _temp) = setup_test_env().await;
757
758 let data = b"Hello, world!";
759 let path = PathBuf::from("/test.txt");
760
761 mount
762 .add(&path, Cursor::new(data.to_vec()), &blobs)
763 .await
764 .unwrap();
765
766 let result = mount.cat(&path, &blobs).await.unwrap();
767 assert_eq!(result, data);
768 }
769
770 #[tokio::test]
771 async fn test_add_with_metadata() {
772 let (mut mount, blobs, _, _temp) = setup_test_env().await;
773
774 let data = b"{ \"key\": \"value\" }";
775 let path = PathBuf::from("/data.json");
776
777 mount
778 .add(&path, Cursor::new(data.to_vec()), &blobs)
779 .await
780 .unwrap();
781
782 let items = mount.ls(&PathBuf::from("/"), &blobs).await.unwrap();
783 assert_eq!(items.len(), 1);
784
785 let (file_path, link) = items.iter().next().unwrap();
786 assert_eq!(file_path, &PathBuf::from("data.json"));
787
788 if let Some(data_info) = link.data() {
789 assert!(data_info.mime().is_some());
790 assert_eq!(data_info.mime().unwrap().as_ref(), "application/json");
791 } else {
792 panic!("Expected data link with metadata");
793 }
794 }
795
796 #[tokio::test]
797 async fn test_ls() {
798 let (mut mount, blobs, _, _temp) = setup_test_env().await;
799
800 mount
801 .add(
802 &PathBuf::from("/file1.txt"),
803 Cursor::new(b"data1".to_vec()),
804 &blobs,
805 )
806 .await
807 .unwrap();
808 mount
809 .add(
810 &PathBuf::from("/file2.txt"),
811 Cursor::new(b"data2".to_vec()),
812 &blobs,
813 )
814 .await
815 .unwrap();
816 mount
817 .add(
818 &PathBuf::from("/dir/file3.txt"),
819 Cursor::new(b"data3".to_vec()),
820 &blobs,
821 )
822 .await
823 .unwrap();
824
825 let items = mount.ls(&PathBuf::from("/"), &blobs).await.unwrap();
826 assert_eq!(items.len(), 3);
827
828 assert!(items.contains_key(&PathBuf::from("file1.txt")));
829 assert!(items.contains_key(&PathBuf::from("file2.txt")));
830 assert!(items.contains_key(&PathBuf::from("dir")));
831
832 let sub_items = mount.ls(&PathBuf::from("/dir"), &blobs).await.unwrap();
833 assert_eq!(sub_items.len(), 1);
834 assert!(sub_items.contains_key(&PathBuf::from("dir/file3.txt")));
835 }
836
837 #[tokio::test]
838 async fn test_ls_deep() {
839 let (mut mount, blobs, _, _temp) = setup_test_env().await;
840
841 mount
842 .add(&PathBuf::from("/a.txt"), Cursor::new(b"a".to_vec()), &blobs)
843 .await
844 .unwrap();
845 mount
846 .add(
847 &PathBuf::from("/dir1/b.txt"),
848 Cursor::new(b"b".to_vec()),
849 &blobs,
850 )
851 .await
852 .unwrap();
853 mount
854 .add(
855 &PathBuf::from("/dir1/dir2/c.txt"),
856 Cursor::new(b"c".to_vec()),
857 &blobs,
858 )
859 .await
860 .unwrap();
861 mount
862 .add(
863 &PathBuf::from("/dir1/dir2/dir3/d.txt"),
864 Cursor::new(b"d".to_vec()),
865 &blobs,
866 )
867 .await
868 .unwrap();
869
870 let all_items = mount.ls_deep(&PathBuf::from("/"), &blobs).await.unwrap();
871
872 assert!(all_items.contains_key(&PathBuf::from("a.txt")));
873 assert!(all_items.contains_key(&PathBuf::from("dir1")));
874 assert!(all_items.contains_key(&PathBuf::from("dir1/b.txt")));
875 assert!(all_items.contains_key(&PathBuf::from("dir1/dir2")));
876 assert!(all_items.contains_key(&PathBuf::from("dir1/dir2/c.txt")));
877 assert!(all_items.contains_key(&PathBuf::from("dir1/dir2/dir3")));
878 assert!(all_items.contains_key(&PathBuf::from("dir1/dir2/dir3/d.txt")));
879 }
880
881 #[tokio::test]
882 async fn test_rm() {
883 let (mut mount, blobs, _, _temp) = setup_test_env().await;
884
885 mount
886 .add(
887 &PathBuf::from("/file1.txt"),
888 Cursor::new(b"data1".to_vec()),
889 &blobs,
890 )
891 .await
892 .unwrap();
893 mount
894 .add(
895 &PathBuf::from("/file2.txt"),
896 Cursor::new(b"data2".to_vec()),
897 &blobs,
898 )
899 .await
900 .unwrap();
901
902 let items = mount.ls(&PathBuf::from("/"), &blobs).await.unwrap();
903 assert_eq!(items.len(), 2);
904
905 mount
906 .rm(&PathBuf::from("/file1.txt"), &blobs)
907 .await
908 .unwrap();
909
910 let items = mount.ls(&PathBuf::from("/"), &blobs).await.unwrap();
911 assert_eq!(items.len(), 1);
912 assert!(items.contains_key(&PathBuf::from("file2.txt")));
913 assert!(!items.contains_key(&PathBuf::from("file1.txt")));
914
915 let result = mount.cat(&PathBuf::from("/file1.txt"), &blobs).await;
916 assert!(result.is_err());
917 }
918
919 #[tokio::test]
920 async fn test_nested_operations() {
921 let (mut mount, blobs, _, _temp) = setup_test_env().await;
922
923 let files = vec![
924 ("/root.txt", b"root" as &[u8]),
925 ("/docs/readme.md", b"readme" as &[u8]),
926 ("/docs/guide.pdf", b"guide" as &[u8]),
927 ("/src/main.rs", b"main" as &[u8]),
928 ("/src/lib.rs", b"lib" as &[u8]),
929 ("/src/tests/unit.rs", b"unit" as &[u8]),
930 ("/src/tests/integration.rs", b"integration" as &[u8]),
931 ];
932
933 for (path, data) in &files {
934 mount
935 .add(&PathBuf::from(path), Cursor::new(data.to_vec()), &blobs)
936 .await
937 .unwrap();
938 }
939
940 for (path, expected_data) in &files {
941 let data = mount.cat(&PathBuf::from(path), &blobs).await.unwrap();
942 assert_eq!(data, expected_data.to_vec());
943 }
944
945 mount
946 .rm(&PathBuf::from("/src/tests/unit.rs"), &blobs)
947 .await
948 .unwrap();
949
950 let result = mount
951 .cat(&PathBuf::from("/src/tests/unit.rs"), &blobs)
952 .await;
953 assert!(result.is_err());
954
955 let data = mount
956 .cat(&PathBuf::from("/src/tests/integration.rs"), &blobs)
957 .await
958 .unwrap();
959 assert_eq!(data, b"integration");
960 }
961
962 #[tokio::test]
963 async fn test_various_file_types() {
964 let (mut mount, blobs, _, _temp) = setup_test_env().await;
965
966 let test_files = vec![
967 ("/image.png", "image/png"),
968 ("/video.mp4", "video/mp4"),
969 ("/style.css", "text/css"),
970 ("/script.js", "application/javascript"),
971 ("/data.json", "application/json"),
972 ("/archive.zip", "application/zip"),
973 ("/document.pdf", "application/pdf"),
974 ("/code.rs", "text/rust"),
975 ];
976
977 for (path, expected_mime) in test_files {
978 mount
979 .add(&PathBuf::from(path), Cursor::new(b"test".to_vec()), &blobs)
980 .await
981 .unwrap();
982
983 let items = mount.ls(&PathBuf::from("/"), &blobs).await.unwrap();
984 let link = items.values().find(|l| l.is_data()).unwrap();
985
986 if let Some(data_info) = link.data() {
987 assert!(data_info.mime().is_some());
988 assert_eq!(data_info.mime().unwrap().as_ref(), expected_mime);
989 }
990
991 mount.rm(&PathBuf::from(path), &blobs).await.unwrap();
992 }
993 }
994
995 #[tokio::test]
996 async fn test_error_cases() {
997 let (mount, blobs, _, _temp) = setup_test_env().await;
998
999 let result = mount
1000 .cat(&PathBuf::from("/does_not_exist.txt"), &blobs)
1001 .await;
1002 assert!(result.is_err());
1003
1004 let result = mount.ls(&PathBuf::from("/does_not_exist"), &blobs).await;
1005 assert!(result.is_err() || result.unwrap().is_empty());
1006
1007 let (mut mount, blobs, _, _temp) = setup_test_env().await;
1008 mount
1009 .add(
1010 &PathBuf::from("/dir/file.txt"),
1011 Cursor::new(b"data".to_vec()),
1012 &blobs,
1013 )
1014 .await
1015 .unwrap();
1016
1017 let result = mount.cat(&PathBuf::from("/dir"), &blobs).await;
1018 assert!(result.is_err());
1019 }
1020
1021 #[tokio::test]
1022 async fn test_save_load() {
1023 let (mount, blobs, secret_key, _temp) = setup_test_env().await;
1024 let link = mount.save(&blobs).await.unwrap();
1025 let _mount = Mount::load(&link, &secret_key, &blobs).await.unwrap();
1026 }
1027}