1use bmart::tools::Sorting;
2use eva_common::{EResult, Error};
3use glob_match::glob_match;
4use serde::{Deserialize, Serialize};
5use std::fs::Metadata;
6use std::path::{Path, PathBuf};
7use tokio::fs;
8
9const MAX_LINK_DEPTH: usize = 5;
10
11#[derive(Debug, Sorting, Serialize, Deserialize)]
12#[sorting(id = "path")]
13pub struct Entry {
14 pub path: PathBuf,
15 #[serde(skip)]
16 pub meta: Option<Metadata>,
17 pub kind: Kind,
18}
19
20async fn resolve_symlink_metadata(path: PathBuf) -> Option<Metadata> {
21 let mut source = path;
22 for _ in 0..MAX_LINK_DEPTH {
23 let Ok(target) = fs::read_link(&source).await else {
24 return None;
25 };
26 let Ok(meta) = fs::metadata(&target).await else {
27 return None;
28 };
29 if meta.is_symlink() {
30 source = target;
31 } else {
32 return Some(meta);
33 }
34 }
35 None
36}
37
38#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Default)]
39#[serde(rename_all = "lowercase")]
40pub enum Kind {
41 File,
42 Dir,
43 #[default]
44 Any,
45 Unknown,
46}
47
48#[async_recursion::async_recursion]
49async fn list_entries_by_mask(
50 path: &Path,
51 masks: &[&str],
52 kind: Kind,
53 result: &mut Vec<Entry>,
54 recursive: bool,
55) -> Result<(), std::io::Error> {
56 let mut entries = fs::read_dir(path).await?;
57 while let Some(entry) = entries.next_entry().await? {
58 let mut meta = entry.metadata().await?;
59 let entry_kind = if meta.is_file() {
60 Kind::File
61 } else if meta.is_dir() {
62 Kind::Dir
63 } else if meta.is_symlink() {
64 if let Some(target_meta) = resolve_symlink_metadata(entry.path()).await {
65 meta = target_meta;
66 if meta.is_file() {
67 Kind::File
68 } else if meta.is_dir() {
69 Kind::Dir
70 } else {
71 Kind::Unknown
72 }
73 } else {
74 Kind::Unknown
75 }
76 } else {
77 Kind::Unknown
78 };
79 if entry_kind == kind || (kind == Kind::Any && entry_kind != Kind::Unknown) {
80 for mask in masks {
81 if glob_match(mask, &entry.file_name().to_string_lossy()) {
82 result.push(Entry {
83 path: entry.path(),
84 meta: Some(meta),
85 kind: entry_kind,
86 });
87 break;
88 }
89 }
90 }
91 if entry_kind == Kind::Dir && recursive {
92 let mut subdir = path.to_owned();
93 subdir.push(entry.file_name());
94 list_entries_by_mask(&subdir, masks, kind, result, true).await?;
95 }
96 }
97 Ok(())
98}
99
100pub async fn list(
101 path: &Path,
102 masks: &[&str],
103 kind: Kind,
104 recursive: bool,
105 absolute: bool,
106) -> EResult<Vec<Entry>> {
107 let mut entries = Vec::new();
108 if let Err(e) = list_entries_by_mask(path, masks, kind, &mut entries, recursive).await {
109 if e.kind() == std::io::ErrorKind::NotFound {
110 return Err(Error::not_found(path.to_string_lossy()));
111 }
112 return Err(e.into());
113 }
114 let s = path.to_string_lossy();
115 let mut result: Vec<Entry> = if absolute {
116 entries
117 } else {
118 entries
119 .into_iter()
120 .filter_map(|r| {
121 let p = r.path.to_string_lossy();
122 if s.len() + 1 < p.len() {
123 Some(Entry {
124 path: Path::new(&p[s.len() + 1..]).to_owned(),
125 meta: r.meta,
126 kind: r.kind,
127 })
128 } else {
129 None
130 }
131 })
132 .collect()
133 };
134 result.sort();
135 Ok(result)
136}
137
138pub async fn safe_list(
139 path: &Path,
140 masks: &[&str],
141 kind: Kind,
142 recursive: bool,
143 absolute: bool,
144) -> EResult<Vec<Entry>> {
145 if path.to_string_lossy().contains("../") {
146 Err(Error::invalid_params("path can not contain ../"))
147 } else {
148 list(path, masks, kind, recursive, absolute).await
149 }
150}