1use std::time::SystemTime;
4
5use crate::{
6 error::{Error, Operation, Result},
7 path::{Path, PathBuf},
8};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12#[non_exhaustive]
13pub enum EntryKind {
14 File,
16 Directory,
18 Symlink,
20 Other,
22}
23
24#[derive(Clone, Debug)]
26pub struct EntryInfo {
27 path: PathBuf,
28 kind: EntryKind,
29 len: u64,
30 modified: Option<SystemTime>,
31}
32
33impl EntryInfo {
34 #[must_use]
36 pub fn path(&self) -> &Path {
37 &self.path
38 }
39
40 #[must_use]
42 pub const fn kind(&self) -> EntryKind {
43 self.kind
44 }
45
46 #[must_use]
48 pub const fn len(&self) -> u64 {
49 self.len
50 }
51
52 #[must_use]
54 pub const fn is_empty(&self) -> bool {
55 self.len == 0
56 }
57
58 #[must_use]
60 pub const fn modified(&self) -> Option<SystemTime> {
61 self.modified
62 }
63}
64
65pub fn ensure(path: &Path) -> Result<()> {
71 std::fs::create_dir_all(path)
72 .map_err(|source| Error::new(Operation::CreateDirectory, path, source))
73}
74
75pub fn create(path: &Path) -> Result<()> {
81 std::fs::create_dir(path).map_err(|source| Error::new(Operation::CreateDirectory, path, source))
82}
83
84pub fn inspect(path: &Path) -> Result<Option<EntryInfo>> {
92 inspect_with(path, true)
93}
94
95pub fn inspect_link(path: &Path) -> Result<Option<EntryInfo>> {
103 inspect_with(path, false)
104}
105
106fn inspect_with(path: &Path, follow: bool) -> Result<Option<EntryInfo>> {
107 let result = if follow {
108 std::fs::metadata(path)
109 } else {
110 std::fs::symlink_metadata(path)
111 };
112 let metadata = match result {
113 Ok(metadata) => metadata,
114 Err(source) if source.kind() == std::io::ErrorKind::NotFound => return Ok(None),
115 Err(source) => return Err(Error::new(Operation::Inspect, path, source)),
116 };
117 let file_type = metadata.file_type();
118 let kind = if file_type.is_file() {
119 EntryKind::File
120 } else if file_type.is_dir() {
121 EntryKind::Directory
122 } else if file_type.is_symlink() {
123 EntryKind::Symlink
124 } else {
125 EntryKind::Other
126 };
127
128 Ok(Some(EntryInfo {
129 path: path.to_path_buf(),
130 kind,
131 len: metadata.len(),
132 modified: metadata.modified().ok(),
133 }))
134}
135
136pub fn entries(path: &Path) -> Result<Vec<EntryInfo>> {
144 let entries = std::fs::read_dir(path)
145 .map_err(|source| Error::new(Operation::ReadDirectory, path, source))?;
146 let mut result = Vec::new();
147
148 for entry in entries {
149 let entry = entry.map_err(|source| Error::new(Operation::ReadDirectory, path, source))?;
150 let entry_path = PathBuf::from(entry.path());
151 let info = inspect_link(&entry_path)?.ok_or_else(|| {
152 Error::new(
153 Operation::Inspect,
154 &entry_path,
155 std::io::Error::new(
156 std::io::ErrorKind::NotFound,
157 "directory entry disappeared during inspection",
158 ),
159 )
160 })?;
161
162 result.push(info);
163 }
164
165 result.sort_by(|left, right| left.path.cmp(&right.path));
166
167 Ok(result)
168}
169
170pub fn remove_file_if_exists(path: &Path) -> Result<bool> {
178 match std::fs::remove_file(path) {
179 Ok(()) => Ok(true),
180 Err(source) if source.kind() == std::io::ErrorKind::NotFound => Ok(false),
181 Err(source) => Err(Error::new(Operation::RemoveFile, path, source)),
182 }
183}
184
185pub fn remove_if_empty(path: &Path) -> Result<()> {
191 std::fs::remove_dir(path).map_err(|source| Error::new(Operation::RemoveDirectory, path, source))
192}
193
194pub fn remove_tree_if_exists(path: &Path) -> Result<bool> {
202 match std::fs::remove_dir_all(path) {
203 Ok(()) => Ok(true),
204 Err(source) if source.kind() == std::io::ErrorKind::NotFound => Ok(false),
205 Err(source) => Err(Error::new(Operation::RemoveDirectory, path, source)),
206 }
207}
208
209pub fn canonicalize(path: &Path) -> Result<PathBuf> {
217 std::fs::canonicalize(path)
218 .map(PathBuf::from)
219 .map_err(|source| Error::new(Operation::Canonicalize, path, source))
220}
221
222pub fn rename(from: &Path, to: &Path) -> Result<()> {
228 std::fs::rename(from, to).map_err(|source| Error::between(Operation::Rename, from, to, source))
229}