1use std::collections::BTreeSet;
3use std::path::Path;
4
5use crate::pre::*;
6
7#[inline(always)]
9pub fn is_empty_dir(path: impl AsRef<Path>) -> crate::Result<bool> {
10 is_empty_dir_impl(path.as_ref())
11}
12fn is_empty_dir_impl(path: &Path) -> crate::Result<bool> {
13 crate::trace!("is_empty_dir '{}'", path.display());
14 let mut x = read_dir_impl(path)?;
15 Ok(x.next().is_none())
16}
17
18#[inline(always)]
21pub fn make_dir(path: impl AsRef<Path>) -> crate::Result<()> {
22 make_dir_impl(path.as_ref())
23}
24fn make_dir_impl(path: &Path) -> crate::Result<()> {
25 crate::trace!("make_dir '{}'", path.display());
26 let (exists, is_dir) = match std::fs::metadata(path) {
27 Ok(m) => (true, m.is_dir()),
28 Err(_) => (false, false),
29 };
30 match (exists, is_dir) {
31 (true, false) => {
32 crate::bail!(
33 "failed to create directory: '{}' exists but is not a directory",
34 path.display()
35 );
36 }
37 (true, true) => {} (false, _) => {
39 crate::trace!("make_dir: creating '{}'", path.display());
40 crate::check!(
41 std::fs::create_dir_all(path),
42 "failed to create directory '{}'",
43 path.display()
44 )?;
45 }
46 }
47 Ok(())
48}
49
50#[cfg(feature = "coroutine")]
52#[inline(always)]
53pub async fn co_make_dir(path: impl AsRef<Path>) -> crate::Result<()> {
54 co_make_dir_impl(path.as_ref()).await
55}
56#[cfg(feature = "coroutine")]
57async fn co_make_dir_impl(path: &Path) -> crate::Result<()> {
58 crate::trace!("co_make_dir '{}'", path.display());
59 let (exists, is_dir) = match std::fs::metadata(path) {
60 Ok(m) => (true, m.is_dir()),
61 Err(_) => (false, false),
62 };
63 match (exists, is_dir) {
64 (true, false) => {
65 crate::bail!(
66 "failed to create directory: '{}' exists but is not a directory",
67 path.display()
68 );
69 }
70 (true, true) => {} (false, _) => {
72 crate::trace!("co_make_dir: creating '{}'", path.display());
73 crate::check!(
74 tokio::fs::create_dir_all(path).await,
75 "failed to create directory '{}'",
76 path.display()
77 )?;
78 }
79 }
80 Ok(())
81}
82
83pub fn make_dir_empty(path: impl AsRef<Path>) -> crate::Result<()> {
88 let path = path.as_ref();
89 make_dir_impl(path)?;
90 remove_contents_impl(path)
91}
92
93#[cfg(feature = "coroutine")]
95pub async fn co_make_dir_empty(path: impl AsRef<Path>) -> crate::Result<()> {
96 let path = path.as_ref();
97 co_make_dir_impl(path).await?;
98 co_remove_contents_impl(path).await
99}
100
101pub fn make_dir_absent_or_empty(path: impl AsRef<Path>) -> crate::Result<()> {
103 let path = path.as_ref();
104 if !path.exists() {
105 return Ok(());
106 }
107 remove_contents(path)
108}
109#[cfg(feature = "coroutine")]
111pub async fn co_make_dir_absent_or_empty(path: impl AsRef<Path>) -> crate::Result<()> {
112 let path = path.as_ref();
113 if !path.exists() {
114 return Ok(());
115 }
116 co_remove_contents(path).await
117}
118
119#[inline(always)]
125pub fn rec_remove(path: impl AsRef<Path>) -> crate::Result<()> {
126 rec_remove_impl(path.as_ref())
127}
128fn rec_remove_impl(path: &Path) -> crate::Result<()> {
129 if !path.exists() {
130 crate::trace!("rec_remove: is absent: '{}'", path.display());
131 return Ok(());
132 }
133 crate::trace!("rec_remove '{}'", path.display());
134 if !path.is_dir() {
135 crate::bail!("'{}' is not a directory", path.display());
136 }
137 crate::check!(
138 std::fs::remove_dir_all(path),
139 "failed to recursively remove '{}'",
140 path.display()
141 )
142}
143
144#[cfg(feature = "coroutine")]
146#[inline(always)]
147pub async fn co_rec_remove(path: impl AsRef<Path>) -> crate::Result<()> {
148 co_rec_remove_impl(path.as_ref()).await
149}
150#[cfg(feature = "coroutine")]
151async fn co_rec_remove_impl(path: &Path) -> crate::Result<()> {
152 if !path.exists() {
153 crate::trace!("co_rec_remove: is absent: '{}'", path.display());
154 return Ok(());
155 }
156 crate::trace!("co_rec_remove '{}'", path.display());
157 if !path.is_dir() {
158 crate::bail!("'{}' is not a directory", path.display());
159 }
160 crate::check!(
161 tokio::fs::remove_dir_all(path).await,
162 "failed to recursively remove '{}'",
163 path.display()
164 )
165}
166
167#[inline(always)]
173pub fn remove_contents(path: impl AsRef<Path>) -> crate::Result<()> {
174 remove_contents_impl(path.as_ref())
175}
176fn remove_contents_impl(path: &Path) -> crate::Result<()> {
177 crate::trace!("remove_contents '{}'", path.display());
178 if !path.is_dir() {
179 if !path.exists() {
180 crate::bail!("'{}' does not exist", path.display());
181 }
182 crate::bail!("'{}' is not a directory", path.display());
183 }
184 for entry in read_dir(path)? {
185 let entry = crate::check!(entry, "failed to read entry inside '{}'", path.display())?;
186 let entry_path = entry.path();
187 let file_type = crate::check!(
188 entry.file_type(),
189 "failed to read entry type for '{}'",
190 entry_path.display()
191 )?;
192 if file_type.is_dir() {
193 rec_remove(entry_path)?;
194 } else {
195 crate::fs::remove(entry_path)?;
196 }
197 }
198 Ok(())
199}
200
201#[cfg(feature = "coroutine")]
204#[inline(always)]
205pub async fn co_remove_contents(path: impl AsRef<Path>) -> crate::Result<()> {
206 co_remove_contents_impl(path.as_ref()).await
207}
208#[cfg(feature = "coroutine")]
209async fn co_remove_contents_impl(path: &Path) -> crate::Result<()> {
210 crate::trace!("remove_contents '{}'", path.display());
211 if !path.is_dir() {
212 if !path.exists() {
213 crate::bail!("'{}' does not exist", path.display());
214 }
215 crate::bail!("'{}' is not a directory", path.display());
216 }
217 let mut x = co_read_dir(path).await?;
218 let mut join_set = tokio::task::JoinSet::new();
220 loop {
221 let entry = x.next_entry().await;
222 let entry = crate::check!(entry, "failed to read entry inside '{}'", path.display())?;
223 let Some(entry) = entry else {
224 break;
225 };
226 let entry_path = entry.path();
227 let file_type = crate::check!(
228 entry.file_type().await,
229 "failed to read entry type for '{}'",
230 entry_path.display()
231 )?;
232 if file_type.is_dir() {
233 join_set.spawn(async move { co_rec_remove(entry_path).await });
234 } else {
235 join_set.spawn(async move { crate::fs::co_remove(entry_path).await });
236 }
237 }
238 let mut has_failure = false;
239 while let Some(x) = join_set.join_next().await {
240 match x {
241 Err(e) => {
242 crate::debug!("failed to remove some entry in '{}': {e}", path.display());
243 has_failure = true;
244 }
245 Ok(Err(e)) => {
246 crate::debug!("failed to remove some entry in '{}': {e}", path.display());
247 has_failure = true;
248 }
249 Ok(Ok(())) => {}
250 }
251 }
252 if has_failure {
253 crate::bail!(
254 "failed to remove one or more entries in '{}'",
255 path.display()
256 );
257 }
258 Ok(())
259}
260
261pub type ReadDir = std::fs::ReadDir;
262#[cfg(feature = "coroutine")]
263pub type CoReadDir = tokio::fs::ReadDir;
264pub type DirEntry = std::fs::DirEntry;
265#[cfg(feature = "coroutine")]
266pub type CoDirEntry = tokio::fs::DirEntry;
267
268#[inline(always)]
270pub fn read_dir(path: impl AsRef<Path>) -> crate::Result<ReadDir> {
271 read_dir_impl(path.as_ref())
272}
273fn read_dir_impl(path: &Path) -> crate::Result<ReadDir> {
274 crate::check!(
275 std::fs::read_dir(path),
276 "cannot read directory '{}'",
277 path.display()
278 )
279}
280
281#[cfg(feature = "coroutine")]
283#[inline(always)]
284pub async fn co_read_dir(path: impl AsRef<Path>) -> crate::Result<CoReadDir> {
285 co_read_dir_impl(path.as_ref()).await
286}
287#[cfg(feature = "coroutine")]
288async fn co_read_dir_impl(path: &Path) -> crate::Result<CoReadDir> {
289 crate::check!(
290 tokio::fs::read_dir(path).await,
291 "cannot read directory '{}'",
292 path.display()
293 )
294}
295
296#[inline(always)]
314pub fn rec_copy_inefficiently(from: impl AsRef<Path>, to: impl AsRef<Path>) -> crate::Result<()> {
315 rec_copy_inefficiently_impl(from.as_ref(), to.as_ref())
316}
317#[crate::context("failed to copy recursively copy '{}' to '{}'", from.display(), to.display())]
318fn rec_copy_inefficiently_impl(from: &Path, to: &Path) -> crate::Result<()> {
319 crate::trace!(
320 "rec_copy_inefficiently from='{}' to='{}'",
321 from.display(),
322 to.display()
323 );
324 if !from.is_dir() {
325 crate::bail!("source does not exist or is not a directory");
326 }
327 let to_meta = to.metadata();
328 if to_meta.is_ok_and(|x| !x.is_dir()) {
329 crate::bail!("target exists and is not a directory");
330 }
331
332 cu::check!(
333 make_dir_impl(to),
334 "rec_copy: failed to create target directory"
335 )?;
336
337 let mut dir_cache = BTreeSet::new();
339 let mut walker = crate::fs::walker(from);
340 walker.include_dir_entries(true);
341 for entry in walker.walk()? {
342 let entry = entry?;
343 let rel_path = entry.rel_path()?;
344 if !entry.is_dir() {
345 let Some(file_name) = entry.file_name() else {
346 cu::trace!(
347 "skipping file with no file name: '{}'",
348 entry.path().display()
349 );
350 continue;
351 };
352 let containing = if entry.depth() > 1 {
353 let parent = cu::check!(
354 rel_path.parent(),
355 "failed to get relative path of containing directory for entry"
356 )?;
357 to.join(parent)
358 } else {
359 to.to_path_buf()
360 };
361 let target = containing.join(file_name);
362 if !dir_cache.contains(&containing) {
364 make_dir_impl(&containing)?;
365 dir_cache.insert(containing);
366 }
367 crate::fs::copy(entry.path(), target)?;
369 } else {
370 let target_path = to.join(rel_path);
371 make_dir_impl(&target_path)?;
372 dir_cache.insert(target_path);
373 }
374 }
375
376 Ok(())
377}