1use crate::{LeafTraverse, NodeTraverse, TraversalNode};
2use memmap2::Mmap;
3use mime::Mime;
4use std::fs::File;
5use std::io;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8use thiserror::Error;
9
10#[derive(Debug, Clone)]
11pub struct FileLeaf {
12 pub name: String,
13 pub kind: Mime,
14 pub content: Arc<Mmap>,
15}
16
17impl LeafTraverse for FileLeaf {
18 fn name(&self) -> &str {
19 &self.name
20 }
21}
22
23#[derive(Debug, Error)]
24pub enum FsTreeError {
25 #[error("failed to read directory: {0}")]
26 ReadDir(#[source] io::Error),
27 #[error("failed to read file metadata: {0}")]
28 Metadata(#[source] io::Error),
29 #[error("failed to open file: {0}")]
30 Open(io::Error),
31}
32
33#[derive(Clone, Debug)]
34pub struct FsNode {
35 abs_path: PathBuf,
36 name: String,
37}
38
39impl FsNode {
40 pub fn new_root(path: PathBuf) -> FsNode {
41 FsNode {
42 abs_path: path,
43 name: "".to_owned(),
44 }
45 }
46
47 fn new(abs_path: PathBuf, name: String) -> Self {
48 Self { abs_path, name }
49 }
50}
51
52impl NodeTraverse for FsNode {
53 type Leaf = FileLeaf;
54
55 type TraverseError = FsTreeError;
56
57 fn name(&self) -> &str {
58 &self.name
59 }
60
61 fn children(
62 &mut self,
63 ) -> Result<impl Iterator<Item = Result<TraversalNode<Self, Self::Leaf>, Self::TraverseError>>, Self::TraverseError>
64 {
65 let entries = match std::fs::read_dir(&self.abs_path) {
66 Ok(entries) => entries,
67 Err(err) => return Err(FsTreeError::ReadDir(err)),
68 };
69
70 Ok(entries.map(|entry| {
71 let entry = entry.map_err(FsTreeError::ReadDir)?;
72 let file_type = entry.file_type().map_err(FsTreeError::Metadata)?;
73 let name = entry.file_name();
74 let abs_path = entry.path();
75 let name = name.to_string_lossy().into_owned();
76 if file_type.is_dir() {
77 Ok(TraversalNode::Node(FsNode::new(abs_path, name)))
78 } else {
79 let file = File::open(entry.path()).map_err(FsTreeError::Open)?;
80 let content = unsafe { Mmap::map(&file) }.map_err(FsTreeError::Open)?;
81 let kind = detect_file_kind(&abs_path, &content);
82 let leaf = FileLeaf {
83 name,
84 kind,
85 content: Arc::new(content),
86 };
87 Ok(TraversalNode::Leaf(leaf))
88 }
89 }))
90 }
91}
92
93fn detect_file_kind(path: &Path, body: &[u8]) -> Mime {
94 if let Some(kind) = infer::get(body)
95 && let Ok(mime) = kind.mime_type().parse()
96 {
97 mime
98 } else if let Some(mime) = mime_guess::from_path(path).first() {
99 mime
100 } else {
101 mime::APPLICATION_OCTET_STREAM
102 }
103}