1use std::collections::BTreeMap;
2use std::io::Read;
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6use tokio::sync::Mutex;
7use uuid::Uuid;
8
9use crate::crypto::{PublicKey, Secret, SecretError, SecretKey, SecretShare};
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 pub height: u64,
39}
40
41impl MountInner {
42 pub fn link(&self) -> &Link {
43 &self.link
44 }
45 pub fn entry(&self) -> &Node {
46 &self.entry
47 }
48 pub fn manifest(&self) -> &Manifest {
49 &self.manifest
50 }
51 pub fn pins(&self) -> &Pins {
52 &self.pins
53 }
54 pub fn height(&self) -> u64 {
55 self.height
56 }
57}
58
59#[derive(Clone)]
60pub struct Mount(Arc<Mutex<MountInner>>, BlobsStore);
61
62#[derive(Debug, thiserror::Error)]
63pub enum MountError {
64 #[error("default error: {0}")]
65 Default(#[from] anyhow::Error),
66 #[error("link not found")]
67 LinkNotFound(Link),
68 #[error("path not found: {0}")]
69 PathNotFound(PathBuf),
70 #[error("path is not a node: {0}")]
71 PathNotNode(PathBuf),
72 #[error("path already exists: {0}")]
73 PathAlreadyExists(PathBuf),
74 #[error("blobs store error: {0}")]
75 BlobsStore(#[from] BlobsStoreError),
76 #[error("secret error: {0}")]
77 Secret(#[from] SecretError),
78 #[error("node error: {0}")]
79 Node(#[from] NodeError),
80 #[error("codec error: {0}")]
81 Codec(#[from] CodecError),
82 #[error("share error: {0}")]
83 Share(#[from] crate::crypto::SecretShareError),
84 #[error("peers share was not found. this should be impossible")]
85 ShareNotFound,
86}
87
88impl Mount {
89 pub async fn inner(&self) -> MountInner {
90 self.0.lock().await.clone()
91 }
92
93 pub fn blobs(&self) -> BlobsStore {
94 self.1.clone()
95 }
96
97 pub async fn link(&self) -> Link {
98 let inner = self.0.lock().await;
99 inner.link.clone()
100 }
101
102 pub async fn save(&self, blobs: &BlobsStore) -> Result<(Link, Link, u64), MountError> {
104 let (entry_node, mut pins, previous_link, previous_height, manifest_template) = {
106 let inner = self.0.lock().await;
107 (
108 inner.entry.clone(),
109 inner.pins.clone(),
110 inner.link.clone(),
111 inner.height,
112 inner.manifest.clone(),
113 )
114 };
115
116 let height = previous_height + 1;
118
119 let secret = Secret::generate();
121
122 let entry = Self::_put_node_in_blobs(&entry_node, &secret, blobs).await?;
124
125 pins.insert(entry.clone().hash());
128 pins.insert(previous_link.hash());
129 let pins_link = Self::_put_pins_in_blobs(&pins, blobs).await?;
130
131 let mut manifest = manifest_template;
134 let _m = manifest.clone();
135 let shares = _m.shares();
136 manifest.unset_shares();
137 for share in shares.values() {
138 let public_key = share.principal().identity;
139 manifest.add_share(public_key, secret.clone())?;
140 }
141 manifest.set_pins(pins_link.clone());
142 manifest.set_previous(previous_link.clone());
143 manifest.set_entry(entry.clone());
144 manifest.set_height(height);
145
146 let link = Self::_put_manifest_in_blobs(&manifest, blobs).await?;
148
149 {
151 let mut inner = self.0.lock().await;
152 inner.manifest = manifest;
153 inner.height = height;
154 }
155
156 Ok((link, previous_link, height))
157 }
158
159 pub async fn init(
160 id: Uuid,
161 name: String,
162 owner: &SecretKey,
163 blobs: &BlobsStore,
164 ) -> Result<Self, MountError> {
165 let entry = Node::default();
167 let secret = Secret::generate();
169 let entry_link = Self::_put_node_in_blobs(&entry, &secret, blobs).await?;
171 let share = SecretShare::new(&secret, &owner.public())?;
173 let mut pins = Pins::new();
175 pins.insert(entry_link.hash());
176 let pins_link = Self::_put_pins_in_blobs(&pins, blobs).await?;
178 let manifest = Manifest::new(
180 id,
181 name.clone(),
182 owner.public(),
183 share,
184 entry_link.clone(),
185 pins_link.clone(),
186 0, );
188 let link = Self::_put_manifest_in_blobs(&manifest, blobs).await?;
189
190 Ok(Mount(
192 Arc::new(Mutex::new(MountInner {
193 link,
194 manifest,
195 entry,
196 pins,
197 height: 0,
198 })),
199 blobs.clone(),
200 ))
201 }
202
203 pub async fn load(
204 link: &Link,
205 secret_key: &SecretKey,
206 blobs: &BlobsStore,
207 ) -> Result<Self, MountError> {
208 let public_key = &secret_key.public();
209 let manifest = Self::_get_manifest_from_blobs(link, blobs).await?;
210
211 let _share = manifest.get_share(public_key);
212
213 let share = match _share {
214 Some(share) => share.share(),
215 None => return Err(MountError::ShareNotFound),
216 };
217
218 let secret = share.recover(secret_key)?;
219
220 let pins = Self::_get_pins_from_blobs(manifest.pins(), blobs).await?;
221 let entry =
222 Self::_get_node_from_blobs(&NodeLink::Dir(manifest.entry().clone(), secret), blobs)
223 .await?;
224
225 let height = manifest.height();
227
228 Ok(Mount(
229 Arc::new(Mutex::new(MountInner {
230 link: link.clone(),
231 manifest,
232 entry,
233 pins,
234 height,
235 })),
236 blobs.clone(),
237 ))
238 }
239
240 pub async fn share(&mut self, peer: PublicKey) -> Result<(), MountError> {
241 let mut inner = self.0.lock().await;
242 inner.manifest.add_share(peer, Secret::default())?;
243 Ok(())
244 }
245
246 pub async fn add<R>(&mut self, path: &Path, data: R) -> Result<(), MountError>
247 where
248 R: Read + Send + Sync + 'static + Unpin,
249 {
250 let secret = Secret::generate();
251
252 let encrypted_reader = secret.encrypt_reader(data)?;
253
254 use bytes::Bytes;
256 use futures::stream;
257 let encrypted_bytes = {
258 let mut buf = Vec::new();
259 let mut reader = encrypted_reader;
260 reader.read_to_end(&mut buf).map_err(SecretError::Io)?;
261 buf
262 };
263
264 let stream = Box::pin(stream::once(async move {
265 Ok::<_, std::io::Error>(Bytes::from(encrypted_bytes))
266 }));
267
268 let hash = self.1.put_stream(stream).await?;
269
270 let link = Link::new(crate::linked_data::LD_RAW_CODEC, hash);
271
272 let node_link = NodeLink::new_data_from_path(link.clone(), secret, path);
273
274 let root_node = {
275 let inner = self.0.lock().await;
276 inner.entry.clone()
277 };
278
279 let (updated_link, node_hashes) =
280 Self::_set_node_link_at_path(root_node, node_link, path, &self.1).await?;
281
282 let new_entry = if let NodeLink::Dir(new_root_link, new_secret) = updated_link {
284 Some(
285 Self::_get_node_from_blobs(
286 &NodeLink::Dir(new_root_link.clone(), new_secret),
287 &self.1,
288 )
289 .await?,
290 )
291 } else {
292 None
293 };
294
295 {
297 let mut inner = self.0.lock().await;
298 inner.pins.insert(hash);
300 inner.pins.extend(node_hashes);
301
302 if let Some(entry) = new_entry {
303 inner.entry = entry;
304 }
305 }
306
307 Ok(())
308 }
309
310 pub async fn rm(&mut self, path: &Path) -> Result<(), MountError> {
311 let path = clean_path(path);
312 let parent_path = path
313 .parent()
314 .ok_or_else(|| MountError::Default(anyhow::anyhow!("Cannot remove root")))?;
315
316 let entry = {
317 let inner = self.0.lock().await;
318 inner.entry.clone()
319 };
320
321 let mut parent_node = if parent_path == Path::new("") {
322 entry.clone()
323 } else {
324 Self::_get_node_at_path(&entry, parent_path, &self.1).await?
325 };
326
327 let file_name = path.file_name().unwrap().to_string_lossy().to_string();
328
329 if parent_node.del(&file_name).is_none() {
330 return Err(MountError::PathNotFound(path.to_path_buf()));
331 }
332
333 if parent_path == Path::new("") {
334 let secret = Secret::generate();
335 let link = Self::_put_node_in_blobs(&parent_node, &secret, &self.1).await?;
336
337 let mut inner = self.0.lock().await;
338 inner.pins.insert(link.hash());
340 inner.entry = parent_node;
341 } else {
342 let secret = Secret::generate();
344 let parent_link = Self::_put_node_in_blobs(&parent_node, &secret, &self.1).await?;
345 let node_link = NodeLink::new_dir(parent_link.clone(), secret);
346
347 let abs_parent_path = Path::new("/").join(parent_path);
349 let (updated_link, node_hashes) =
350 Self::_set_node_link_at_path(entry, node_link, &abs_parent_path, &self.1).await?;
351
352 let new_entry = if let NodeLink::Dir(new_root_link, new_secret) = updated_link {
353 Some(
354 Self::_get_node_from_blobs(
355 &NodeLink::Dir(new_root_link.clone(), new_secret),
356 &self.1,
357 )
358 .await?,
359 )
360 } else {
361 None
362 };
363
364 let mut inner = self.0.lock().await;
365 inner.pins.insert(parent_link.hash());
367 inner.pins.extend(node_hashes);
368
369 if let Some(new_entry) = new_entry {
370 inner.entry = new_entry;
371 }
372 }
373
374 Ok(())
375 }
376
377 pub async fn mkdir(&mut self, path: &Path) -> Result<(), MountError> {
378 let path = clean_path(path);
379
380 let entry = {
382 let inner = self.0.lock().await;
383 inner.entry.clone()
384 };
385
386 let (parent_path, dir_name) = if let Some(parent) = path.parent() {
388 (
389 parent,
390 path.file_name().unwrap().to_string_lossy().to_string(),
391 )
392 } else {
393 return Err(MountError::Default(anyhow::anyhow!("Cannot mkdir at root")));
394 };
395
396 let parent_node = if parent_path == Path::new("") {
398 entry.clone()
399 } else {
400 match Self::_get_node_at_path(&entry, parent_path, &self.1).await {
402 Ok(node) => node,
403 Err(MountError::PathNotFound(_)) => Node::default(), Err(err) => return Err(err),
405 }
406 };
407
408 if parent_node.get_link(&dir_name).is_some() {
410 return Err(MountError::PathAlreadyExists(Path::new("/").join(&path)));
411 }
412
413 let new_dir_node = Node::default();
415
416 let secret = Secret::generate();
418
419 let dir_link = Self::_put_node_in_blobs(&new_dir_node, &secret, &self.1).await?;
421
422 let node_link = NodeLink::new_dir(dir_link.clone(), secret);
424
425 let abs_path = Path::new("/").join(&path);
427
428 let (updated_link, node_hashes) =
430 Self::_set_node_link_at_path(entry, node_link, &abs_path, &self.1).await?;
431
432 let new_entry = if let NodeLink::Dir(new_root_link, new_secret) = updated_link {
434 Some(
435 Self::_get_node_from_blobs(
436 &NodeLink::Dir(new_root_link.clone(), new_secret),
437 &self.1,
438 )
439 .await?,
440 )
441 } else {
442 None
443 };
444
445 {
447 let mut inner = self.0.lock().await;
448 inner.pins.insert(dir_link.hash());
450 inner.pins.extend(node_hashes);
451
452 if let Some(new_entry) = new_entry {
453 inner.entry = new_entry;
454 }
455 }
456
457 Ok(())
458 }
459
460 pub async fn ls(&self, path: &Path) -> Result<BTreeMap<PathBuf, NodeLink>, MountError> {
461 let mut items = BTreeMap::new();
462 let path = clean_path(path);
463
464 let inner = self.0.lock().await;
465 let root_node = inner.entry.clone();
466 drop(inner);
467
468 let node = if path == Path::new("") {
469 root_node
470 } else {
471 match Self::_get_node_at_path(&root_node, &path, &self.1).await {
472 Ok(node) => node,
473 Err(MountError::LinkNotFound(_)) => {
474 return Err(MountError::PathNotNode(path.to_path_buf()))
475 }
476 Err(err) => return Err(err),
477 }
478 };
479
480 for (name, link) in node.get_links() {
481 let mut full_path = path.clone();
482 full_path.push(name);
483 items.insert(full_path, link.clone());
484 }
485
486 Ok(items)
487 }
488
489 pub async fn ls_deep(&self, path: &Path) -> Result<BTreeMap<PathBuf, NodeLink>, MountError> {
490 let base_path = clean_path(path);
491 self._ls_deep(path, &base_path).await
492 }
493
494 async fn _ls_deep(
495 &self,
496 path: &Path,
497 base_path: &Path,
498 ) -> Result<BTreeMap<PathBuf, NodeLink>, MountError> {
499 let mut all_items = BTreeMap::new();
500
501 let items = self.ls(path).await?;
503
504 for (item_path, link) in items {
505 let relative_path = if base_path == Path::new("") {
507 item_path.clone()
508 } else {
509 item_path
510 .strip_prefix(base_path)
511 .unwrap_or(&item_path)
512 .to_path_buf()
513 };
514 all_items.insert(relative_path.clone(), link.clone());
515
516 if link.is_dir() {
517 let abs_item_path = Path::new("/").join(&item_path);
519 let sub_items = Box::pin(self._ls_deep(&abs_item_path, base_path)).await?;
520
521 for (sub_path, sub_link) in sub_items {
523 all_items.insert(sub_path, sub_link);
524 }
525 }
526 }
527
528 Ok(all_items)
529 }
530
531 #[allow(clippy::await_holding_lock)]
532 pub async fn cat(&self, path: &Path) -> Result<Vec<u8>, MountError> {
533 let path = clean_path(path);
534
535 let inner = self.0.lock().await;
536 let root_node = inner.entry.clone();
537 drop(inner);
538
539 let (parent_path, file_name) = if let Some(parent) = path.parent() {
540 (
541 parent,
542 path.file_name().unwrap().to_string_lossy().to_string(),
543 )
544 } else {
545 return Err(MountError::PathNotFound(path.to_path_buf()));
546 };
547
548 let parent_node = if parent_path == Path::new("") {
549 root_node
550 } else {
551 Self::_get_node_at_path(&root_node, parent_path, &self.1).await?
552 };
553
554 let link = parent_node
555 .get_link(&file_name)
556 .ok_or_else(|| MountError::PathNotFound(path.to_path_buf()))?;
557
558 match link {
559 NodeLink::Data(link, secret, _) => {
560 let encrypted_data = self.1.get(&link.hash()).await?;
561 let data = secret.decrypt(&encrypted_data)?;
562 Ok(data)
563 }
564 NodeLink::Dir(_, _) => Err(MountError::PathNotNode(path.to_path_buf())),
565 }
566 }
567
568 #[allow(clippy::await_holding_lock)]
570 pub async fn get(&self, path: &Path) -> Result<NodeLink, MountError> {
571 let path = clean_path(path);
572
573 let inner = self.0.lock().await;
574 let root_node = inner.entry.clone();
575 drop(inner);
576
577 let (parent_path, file_name) = if let Some(parent) = path.parent() {
578 (
579 parent,
580 path.file_name().unwrap().to_string_lossy().to_string(),
581 )
582 } else {
583 return Err(MountError::PathNotFound(path.to_path_buf()));
584 };
585
586 let parent_node = if parent_path == Path::new("") {
587 root_node
588 } else {
589 Self::_get_node_at_path(&root_node, parent_path, &self.1).await?
590 };
591
592 parent_node
593 .get_link(&file_name)
594 .cloned()
595 .ok_or_else(|| MountError::PathNotFound(path.to_path_buf()))
596 }
597
598 async fn _get_node_at_path(
599 node: &Node,
600 path: &Path,
601 blobs: &BlobsStore,
602 ) -> Result<Node, MountError> {
603 let mut current_node = node.clone();
604 let mut consumed_path = PathBuf::from("/");
605
606 for part in path.iter() {
607 consumed_path.push(part);
608 let next = part.to_string_lossy().to_string();
609 let next_link = current_node
610 .get_link(&next)
611 .ok_or(MountError::PathNotFound(consumed_path.clone()))?;
612 current_node = Self::_get_node_from_blobs(next_link, blobs).await?
613 }
614 Ok(current_node)
615 }
616
617 pub async fn _set_node_link_at_path(
618 node: Node,
619 node_link: NodeLink,
620 path: &Path,
621 blobs: &BlobsStore,
622 ) -> Result<(NodeLink, Vec<crate::linked_data::Hash>), MountError> {
623 let path = clean_path(path);
624 let mut visited_nodes = Vec::new();
625 let mut name = path.file_name().unwrap().to_string_lossy().to_string();
626 let parent_path = path.parent().unwrap_or(Path::new(""));
627
628 let mut consumed_path = PathBuf::from("/");
629 let mut node = node;
630 visited_nodes.push((consumed_path.clone(), node.clone()));
631
632 for part in parent_path.iter() {
633 let next = part.to_string_lossy().to_string();
634 let next_link = node.get_link(&next);
635 if let Some(next_link) = next_link {
636 consumed_path.push(part);
637 match next_link {
638 NodeLink::Dir(..) => {
639 node = Self::_get_node_from_blobs(next_link, blobs).await?
640 }
641 NodeLink::Data(..) => {
642 return Err(MountError::PathNotNode(consumed_path.clone()));
643 }
644 }
645 visited_nodes.push((consumed_path.clone(), node.clone()));
646 } else {
647 node = Node::default();
649 consumed_path.push(part);
650 visited_nodes.push((consumed_path.clone(), node.clone()));
651 }
652 }
653
654 let mut node_link = node_link;
655 let mut created_hashes = Vec::new();
656 for (path, mut node) in visited_nodes.into_iter().rev() {
657 node.insert(name, node_link.clone());
658 let secret = Secret::generate();
659 let link = Self::_put_node_in_blobs(&node, &secret, blobs).await?;
660 created_hashes.push(link.hash());
661 node_link = NodeLink::Dir(link, secret);
662 name = path
663 .file_name()
664 .unwrap_or_default()
665 .to_string_lossy()
666 .to_string();
667 }
668
669 Ok((node_link, created_hashes))
670 }
671
672 async fn _get_manifest_from_blobs(
673 link: &Link,
674 blobs: &BlobsStore,
675 ) -> Result<Manifest, MountError> {
676 tracing::debug!(
677 "_get_bucket_from_blobs: Checking for bucket data at link {:?}",
678 link
679 );
680 let hash = link.hash();
681 tracing::debug!("_get_bucket_from_blobs: Bucket hash: {}", hash);
682
683 match blobs.stat(&hash).await {
684 Ok(true) => {
685 tracing::debug!(
686 "_get_bucket_from_blobs: Bucket hash {} exists in blobs",
687 hash
688 );
689 }
690 Ok(false) => {
691 tracing::error!("_get_bucket_from_blobs: Bucket hash {} NOT FOUND in blobs - LinkNotFound error!", hash);
692 return Err(MountError::LinkNotFound(link.clone()));
693 }
694 Err(e) => {
695 tracing::error!(
696 "_get_bucket_from_blobs: Error checking bucket hash {}: {}",
697 hash,
698 e
699 );
700 return Err(e.into());
701 }
702 }
703
704 tracing::debug!("_get_bucket_from_blobs: Reading bucket data from blobs");
705 let data = blobs.get(&hash).await?;
706 tracing::debug!(
707 "_get_bucket_from_blobs: Got {} bytes of bucket data",
708 data.len()
709 );
710
711 let bucket_data = Manifest::decode(&data)?;
712 tracing::debug!(
713 "_get_bucket_from_blobs: Successfully decoded BucketData for bucket '{}'",
714 bucket_data.name()
715 );
716
717 Ok(bucket_data)
718 }
719
720 pub async fn _get_pins_from_blobs(link: &Link, blobs: &BlobsStore) -> Result<Pins, MountError> {
721 tracing::debug!("_get_pins_from_blobs: Checking for pins at link {:?}", link);
722 let hash = link.hash();
723 tracing::debug!("_get_pins_from_blobs: Pins hash: {}", hash);
724
725 match blobs.stat(&hash).await {
726 Ok(true) => {
727 tracing::debug!("_get_pins_from_blobs: Pins hash {} exists in blobs", hash);
728 }
729 Ok(false) => {
730 tracing::error!(
731 "_get_pins_from_blobs: Pins hash {} NOT FOUND in blobs - LinkNotFound error!",
732 hash
733 );
734 return Err(MountError::LinkNotFound(link.clone()));
735 }
736 Err(e) => {
737 tracing::error!(
738 "_get_pins_from_blobs: Error checking pins hash {}: {}",
739 hash,
740 e
741 );
742 return Err(e.into());
743 }
744 }
745
746 tracing::debug!("_get_pins_from_blobs: Reading hash list from blobs");
747 let hashes = blobs.read_hash_list(hash).await?;
749 tracing::debug!(
750 "_get_pins_from_blobs: Successfully read {} hashes from pinset",
751 hashes.len()
752 );
753
754 Ok(Pins::from_vec(hashes))
755 }
756
757 async fn _get_node_from_blobs(
758 node_link: &NodeLink,
759 blobs: &BlobsStore,
760 ) -> Result<Node, MountError> {
761 let link = node_link.link();
762 let secret = node_link.secret();
763 let hash = link.hash();
764
765 tracing::debug!("_get_node_from_blobs: Checking for node at hash {}", hash);
766
767 match blobs.stat(&hash).await {
768 Ok(true) => {
769 tracing::debug!("_get_node_from_blobs: Node hash {} exists in blobs", hash);
770 }
771 Ok(false) => {
772 tracing::error!(
773 "_get_node_from_blobs: Node hash {} NOT FOUND in blobs - LinkNotFound error!",
774 hash
775 );
776 return Err(MountError::LinkNotFound(link.clone()));
777 }
778 Err(e) => {
779 tracing::error!(
780 "_get_node_from_blobs: Error checking node hash {}: {}",
781 hash,
782 e
783 );
784 return Err(e.into());
785 }
786 }
787
788 tracing::debug!("_get_node_from_blobs: Reading encrypted node blob");
789 let blob = blobs.get(&hash).await?;
790 tracing::debug!(
791 "_get_node_from_blobs: Got {} bytes of encrypted node data",
792 blob.len()
793 );
794
795 tracing::debug!("_get_node_from_blobs: Decrypting node data");
796 let data = secret.decrypt(&blob)?;
797 tracing::debug!("_get_node_from_blobs: Decrypted {} bytes", data.len());
798
799 let node = Node::decode(&data)?;
800 tracing::debug!("_get_node_from_blobs: Successfully decoded Node");
801
802 Ok(node)
803 }
804
805 async fn _put_node_in_blobs(
809 node: &Node,
810 secret: &Secret,
811 blobs: &BlobsStore,
812 ) -> Result<Link, MountError> {
813 let _data = node.encode()?;
814 let data = secret.encrypt(&_data)?;
815 let hash = blobs.put(data).await?;
816 let link = Link::new(crate::linked_data::LD_RAW_CODEC, hash);
819 Ok(link)
820 }
821
822 pub async fn _put_manifest_in_blobs(
823 bucket_data: &Manifest,
824 blobs: &BlobsStore,
825 ) -> Result<Link, MountError> {
826 let data = bucket_data.encode()?;
827 let hash = blobs.put(data).await?;
828 let link = Link::new(bucket_data.codec(), hash);
831 Ok(link)
832 }
833
834 pub async fn _put_pins_in_blobs(pins: &Pins, blobs: &BlobsStore) -> Result<Link, MountError> {
835 let hash = blobs.create_hash_list(pins.iter().copied()).await?;
837 let link = Link::new(crate::linked_data::LD_RAW_CODEC, hash);
840 Ok(link)
841 }
842}
843
844#[cfg(test)]
845mod test {
846 use super::*;
847 use std::io::Cursor;
848 use tempfile::TempDir;
849
850 async fn setup_test_env() -> (Mount, BlobsStore, crate::crypto::SecretKey, TempDir) {
851 let temp_dir = TempDir::new().unwrap();
852 let blob_path = temp_dir.path().join("blobs");
853
854 let secret_key = SecretKey::generate();
855 let blobs = BlobsStore::fs(&blob_path).await.unwrap();
857
858 let mount = Mount::init(Uuid::new_v4(), "test".to_string(), &secret_key, &blobs)
859 .await
860 .unwrap();
861
862 (mount, blobs, secret_key, temp_dir)
863 }
864
865 #[tokio::test]
866 async fn test_add_and_cat() {
867 let (mut mount, _, _, _temp) = setup_test_env().await;
868
869 let data = b"Hello, world!";
870 let path = PathBuf::from("/test.txt");
871
872 mount.add(&path, Cursor::new(data.to_vec())).await.unwrap();
873
874 let result = mount.cat(&path).await.unwrap();
875 assert_eq!(result, data);
876 }
877
878 #[tokio::test]
879 async fn test_add_with_metadata() {
880 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
881
882 let data = b"{ \"key\": \"value\" }";
883 let path = PathBuf::from("/data.json");
884
885 mount.add(&path, Cursor::new(data.to_vec())).await.unwrap();
886
887 let items = mount.ls(&PathBuf::from("/")).await.unwrap();
888 assert_eq!(items.len(), 1);
889
890 let (file_path, link) = items.iter().next().unwrap();
891 assert_eq!(file_path, &PathBuf::from("data.json"));
892
893 if let Some(data_info) = link.data() {
894 assert!(data_info.mime().is_some());
895 assert_eq!(data_info.mime().unwrap().as_ref(), "application/json");
896 } else {
897 panic!("Expected data link with metadata");
898 }
899 }
900
901 #[tokio::test]
902 async fn test_ls() {
903 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
904
905 mount
906 .add(&PathBuf::from("/file1.txt"), Cursor::new(b"data1".to_vec()))
907 .await
908 .unwrap();
909 mount
910 .add(&PathBuf::from("/file2.txt"), Cursor::new(b"data2".to_vec()))
911 .await
912 .unwrap();
913 mount
914 .add(
915 &PathBuf::from("/dir/file3.txt"),
916 Cursor::new(b"data3".to_vec()),
917 )
918 .await
919 .unwrap();
920
921 let items = mount.ls(&PathBuf::from("/")).await.unwrap();
922 assert_eq!(items.len(), 3);
923
924 assert!(items.contains_key(&PathBuf::from("file1.txt")));
925 assert!(items.contains_key(&PathBuf::from("file2.txt")));
926 assert!(items.contains_key(&PathBuf::from("dir")));
927
928 let sub_items = mount.ls(&PathBuf::from("/dir")).await.unwrap();
929 assert_eq!(sub_items.len(), 1);
930 assert!(sub_items.contains_key(&PathBuf::from("dir/file3.txt")));
931 }
932
933 #[tokio::test]
934 async fn test_ls_deep() {
935 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
936
937 mount
938 .add(&PathBuf::from("/a.txt"), Cursor::new(b"a".to_vec()))
939 .await
940 .unwrap();
941 mount
942 .add(&PathBuf::from("/dir1/b.txt"), Cursor::new(b"b".to_vec()))
943 .await
944 .unwrap();
945 mount
946 .add(
947 &PathBuf::from("/dir1/dir2/c.txt"),
948 Cursor::new(b"c".to_vec()),
949 )
950 .await
951 .unwrap();
952 mount
953 .add(
954 &PathBuf::from("/dir1/dir2/dir3/d.txt"),
955 Cursor::new(b"d".to_vec()),
956 )
957 .await
958 .unwrap();
959
960 let all_items = mount.ls_deep(&PathBuf::from("/")).await.unwrap();
961
962 assert!(all_items.contains_key(&PathBuf::from("a.txt")));
963 assert!(all_items.contains_key(&PathBuf::from("dir1")));
964 assert!(all_items.contains_key(&PathBuf::from("dir1/b.txt")));
965 assert!(all_items.contains_key(&PathBuf::from("dir1/dir2")));
966 assert!(all_items.contains_key(&PathBuf::from("dir1/dir2/c.txt")));
967 assert!(all_items.contains_key(&PathBuf::from("dir1/dir2/dir3")));
968 assert!(all_items.contains_key(&PathBuf::from("dir1/dir2/dir3/d.txt")));
969 }
970
971 #[tokio::test]
972 async fn test_rm() {
973 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
974
975 mount
976 .add(&PathBuf::from("/file1.txt"), Cursor::new(b"data1".to_vec()))
977 .await
978 .unwrap();
979 mount
980 .add(&PathBuf::from("/file2.txt"), Cursor::new(b"data2".to_vec()))
981 .await
982 .unwrap();
983
984 let items = mount.ls(&PathBuf::from("/")).await.unwrap();
985 assert_eq!(items.len(), 2);
986
987 mount.rm(&PathBuf::from("/file1.txt")).await.unwrap();
988
989 let items = mount.ls(&PathBuf::from("/")).await.unwrap();
990 assert_eq!(items.len(), 1);
991 assert!(items.contains_key(&PathBuf::from("file2.txt")));
992 assert!(!items.contains_key(&PathBuf::from("file1.txt")));
993
994 let result = mount.cat(&PathBuf::from("/file1.txt")).await;
995 assert!(result.is_err());
996 }
997
998 #[tokio::test]
999 async fn test_nested_operations() {
1000 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1001
1002 let files = vec![
1003 ("/root.txt", b"root" as &[u8]),
1004 ("/docs/readme.md", b"readme" as &[u8]),
1005 ("/docs/guide.pdf", b"guide" as &[u8]),
1006 ("/src/main.rs", b"main" as &[u8]),
1007 ("/src/lib.rs", b"lib" as &[u8]),
1008 ("/src/tests/unit.rs", b"unit" as &[u8]),
1009 ("/src/tests/integration.rs", b"integration" as &[u8]),
1010 ];
1011
1012 for (path, data) in &files {
1013 mount
1014 .add(&PathBuf::from(path), Cursor::new(data.to_vec()))
1015 .await
1016 .unwrap();
1017 }
1018
1019 for (path, expected_data) in &files {
1020 let data = mount.cat(&PathBuf::from(path)).await.unwrap();
1021 assert_eq!(data, expected_data.to_vec());
1022 }
1023
1024 mount
1025 .rm(&PathBuf::from("/src/tests/unit.rs"))
1026 .await
1027 .unwrap();
1028
1029 let result = mount.cat(&PathBuf::from("/src/tests/unit.rs")).await;
1030 assert!(result.is_err());
1031
1032 let data = mount
1033 .cat(&PathBuf::from("/src/tests/integration.rs"))
1034 .await
1035 .unwrap();
1036 assert_eq!(data, b"integration");
1037 }
1038
1039 #[tokio::test]
1040 async fn test_various_file_types() {
1041 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1042
1043 let test_files = vec![
1044 ("/image.png", "image/png"),
1045 ("/video.mp4", "video/mp4"),
1046 ("/style.css", "text/css"),
1047 ("/script.js", "text/javascript"),
1048 ("/data.json", "application/json"),
1049 ("/archive.zip", "application/zip"),
1050 ("/document.pdf", "application/pdf"),
1051 ("/code.rs", "text/x-rust"),
1052 ];
1053
1054 for (path, expected_mime) in test_files {
1055 mount
1056 .add(&PathBuf::from(path), Cursor::new(b"test".to_vec()))
1057 .await
1058 .unwrap();
1059
1060 let items = mount.ls(&PathBuf::from("/")).await.unwrap();
1061 let link = items.values().find(|l| l.is_data()).unwrap();
1062
1063 if let Some(data_info) = link.data() {
1064 assert!(data_info.mime().is_some());
1065 assert_eq!(data_info.mime().unwrap().as_ref(), expected_mime);
1066 }
1067
1068 mount.rm(&PathBuf::from(path)).await.unwrap();
1069 }
1070 }
1071
1072 #[tokio::test]
1073 async fn test_error_cases() {
1074 let (mount, _blobs, _, _temp) = setup_test_env().await;
1075
1076 let result = mount.cat(&PathBuf::from("/does_not_exist.txt")).await;
1077 assert!(result.is_err());
1078
1079 let result = mount.ls(&PathBuf::from("/does_not_exist")).await;
1080 assert!(result.is_err() || result.unwrap().is_empty());
1081
1082 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1083 mount
1084 .add(
1085 &PathBuf::from("/dir/file.txt"),
1086 Cursor::new(b"data".to_vec()),
1087 )
1088 .await
1089 .unwrap();
1090
1091 let result = mount.cat(&PathBuf::from("/dir")).await;
1092 assert!(result.is_err());
1093 }
1094
1095 #[tokio::test]
1096 async fn test_save_load() {
1097 let (mount, blobs, secret_key, _temp) = setup_test_env().await;
1098 let (link, _previous_link, height) = mount.save(&blobs).await.unwrap();
1099 assert_eq!(height, 1); let loaded_mount = Mount::load(&link, &secret_key, &blobs).await.unwrap();
1101 assert_eq!(loaded_mount.inner().await.height(), 1);
1102 }
1103
1104 #[tokio::test]
1105 async fn test_mkdir() {
1106 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1107
1108 mount.mkdir(&PathBuf::from("/test_dir")).await.unwrap();
1110
1111 let items = mount.ls(&PathBuf::from("/")).await.unwrap();
1113 assert_eq!(items.len(), 1);
1114 assert!(items.get(&PathBuf::from("test_dir")).unwrap().is_dir());
1115 }
1116
1117 #[tokio::test]
1118 async fn test_mkdir_nested() {
1119 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1120
1121 mount.mkdir(&PathBuf::from("/a/b/c")).await.unwrap();
1123
1124 let items_root = mount.ls(&PathBuf::from("/")).await.unwrap();
1126 assert!(items_root.contains_key(&PathBuf::from("a")));
1127
1128 let items_a = mount.ls(&PathBuf::from("/a")).await.unwrap();
1129 assert!(items_a.contains_key(&PathBuf::from("a/b")));
1130
1131 let items_b = mount.ls(&PathBuf::from("/a/b")).await.unwrap();
1132 assert!(items_b.contains_key(&PathBuf::from("a/b/c")));
1133 assert!(items_b.get(&PathBuf::from("a/b/c")).unwrap().is_dir());
1134 }
1135
1136 #[tokio::test]
1137 async fn test_mkdir_already_exists() {
1138 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1139
1140 mount.mkdir(&PathBuf::from("/test_dir")).await.unwrap();
1142
1143 let result = mount.mkdir(&PathBuf::from("/test_dir")).await;
1145 assert!(matches!(result, Err(MountError::PathAlreadyExists(_))));
1146 }
1147
1148 #[tokio::test]
1149 async fn test_mkdir_file_exists() {
1150 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1151
1152 mount
1154 .add(&PathBuf::from("/test.txt"), Cursor::new(b"data".to_vec()))
1155 .await
1156 .unwrap();
1157
1158 let result = mount.mkdir(&PathBuf::from("/test.txt")).await;
1160 assert!(matches!(result, Err(MountError::PathAlreadyExists(_))));
1161 }
1162
1163 #[tokio::test]
1164 async fn test_mkdir_then_add_file() {
1165 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1166
1167 mount.mkdir(&PathBuf::from("/docs")).await.unwrap();
1169
1170 mount
1172 .add(
1173 &PathBuf::from("/docs/readme.md"),
1174 Cursor::new(b"# README".to_vec()),
1175 )
1176 .await
1177 .unwrap();
1178
1179 let data = mount.cat(&PathBuf::from("/docs/readme.md")).await.unwrap();
1181 assert_eq!(data, b"# README");
1182
1183 let items = mount.ls(&PathBuf::from("/docs")).await.unwrap();
1185 assert_eq!(items.len(), 1);
1186 assert!(items.contains_key(&PathBuf::from("docs/readme.md")));
1187 }
1188
1189 #[tokio::test]
1190 async fn test_mkdir_multiple_siblings() {
1191 let (mut mount, _blobs, _, _temp) = setup_test_env().await;
1192
1193 mount.mkdir(&PathBuf::from("/dir1")).await.unwrap();
1195 mount.mkdir(&PathBuf::from("/dir2")).await.unwrap();
1196 mount.mkdir(&PathBuf::from("/dir3")).await.unwrap();
1197
1198 let items = mount.ls(&PathBuf::from("/")).await.unwrap();
1200 assert_eq!(items.len(), 3);
1201 assert!(items.get(&PathBuf::from("dir1")).unwrap().is_dir());
1202 assert!(items.get(&PathBuf::from("dir2")).unwrap().is_dir());
1203 assert!(items.get(&PathBuf::from("dir3")).unwrap().is_dir());
1204 }
1205}