1use std::path::{
4 Path,
5 PathBuf,
6};
7
8use tokio::io::AsyncReadExt;
9
10use crate::{
11 AsyncFile,
12 FsError,
13 Operation,
14};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct FileMetadata {
19 pub length: u64,
21 pub is_directory: bool,
23}
24
25pub async fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf, FsError> {
27 let path = path.as_ref();
28 tokio::fs::canonicalize(path)
29 .await
30 .map_err(|error| FsError::io(Operation::Read, path, error))
31}
32
33pub async fn read_bounded(path: impl AsRef<Path>, max_bytes: usize) -> Result<Vec<u8>, FsError> {
39 let path = path.as_ref();
40 let file = AsyncFile::open(path).await?;
41 let read_limit = max_bytes
42 .checked_add(1)
43 .ok_or_else(|| FsError::InvalidRequest("read bound overflow".to_owned()))?;
44 let mut output = Vec::with_capacity(max_bytes.min(64 * 1024));
46 file.take(read_limit as u64)
47 .read_to_end(&mut output)
48 .await
49 .map_err(|error| FsError::io(Operation::Read, path, error))?;
50 if output.len() > max_bytes {
51 return Err(FsError::InvalidRequest(format!(
52 "file {} exceeds the {} byte read bound",
53 path.display(),
54 max_bytes
55 )));
56 }
57 Ok(output)
58}
59
60pub async fn read_string_bounded(
62 path: impl AsRef<Path>,
63 max_bytes: usize,
64) -> Result<String, FsError> {
65 let path = path.as_ref();
66 let bytes = read_bounded(path, max_bytes).await?;
67 String::from_utf8(bytes).map_err(|error| {
68 FsError::io(
69 Operation::Read,
70 path,
71 format!("file is not valid UTF-8: {error}"),
72 )
73 })
74}
75
76pub async fn read_string_bounded_if_exists(
79 path: impl AsRef<Path>,
80 max_bytes: usize,
81) -> Result<Option<String>, FsError> {
82 let path = path.as_ref();
83 let Some(file) = AsyncFile::open_if_exists(path).await? else {
84 return Ok(None);
85 };
86 let read_limit = max_bytes
87 .checked_add(1)
88 .ok_or_else(|| FsError::InvalidRequest("read bound overflow".to_owned()))?;
89 let mut output = Vec::with_capacity(max_bytes.min(64 * 1024));
91 file.take(read_limit as u64)
92 .read_to_end(&mut output)
93 .await
94 .map_err(|error| FsError::io(Operation::Read, path, error))?;
95 if output.len() > max_bytes {
96 return Err(FsError::InvalidRequest(format!(
97 "file {} exceeds the {} byte read bound",
98 path.display(),
99 max_bytes
100 )));
101 }
102 String::from_utf8(output)
103 .map(Some)
104 .map_err(|error| FsError::io(Operation::Read, path, error))
105}
106
107pub async fn metadata(path: impl AsRef<Path>) -> Result<FileMetadata, FsError> {
109 let path = path.as_ref();
110 let metadata = tokio::fs::metadata(path)
111 .await
112 .map_err(|error| FsError::io(Operation::Read, path, error))?;
113 Ok(FileMetadata {
114 length: metadata.len(),
115 is_directory: metadata.is_dir(),
116 })
117}
118
119pub async fn symlink_metadata(path: impl AsRef<Path>) -> Result<std::fs::Metadata, FsError> {
121 let path = path.as_ref();
122 tokio::fs::symlink_metadata(path)
123 .await
124 .map_err(|error| FsError::io(Operation::Read, path, error))
125}
126
127pub async fn set_permissions(
129 path: impl AsRef<Path>,
130 permissions: std::fs::Permissions,
131) -> Result<(), FsError> {
132 let path = path.as_ref();
133 tokio::fs::set_permissions(path, permissions)
134 .await
135 .map_err(|error| FsError::io(Operation::Write, path, error))
136}
137
138pub async fn try_exists(path: impl AsRef<Path>) -> Result<bool, FsError> {
140 let path = path.as_ref();
141 match tokio::fs::metadata(path).await {
142 Ok(_) => Ok(true),
143 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
144 Err(error) => Err(FsError::io(Operation::Read, path, error)),
145 }
146}
147
148pub async fn write_bytes(path: impl AsRef<Path>, bytes: &[u8]) -> Result<(), FsError> {
150 let path = path.as_ref();
151 let mut file = AsyncFile::create(path).await?;
152 file.write_all(bytes).await?;
153 file.flush().await
154}
155
156pub async fn remove_if_exists(path: impl AsRef<Path>) -> Result<(), FsError> {
158 let path = path.as_ref();
159 match tokio::fs::remove_file(path).await {
160 Ok(()) => Ok(()),
161 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
162 Err(error) => Err(FsError::io(Operation::Write, path, error)),
163 }
164}
165
166pub async fn remove_file(path: impl AsRef<Path>) -> Result<(), FsError> {
168 let path = path.as_ref();
169 tokio::fs::remove_file(path)
170 .await
171 .map_err(|error| FsError::io(Operation::Write, path, error))
172}
173
174pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<(), FsError> {
176 let from = from.as_ref();
177 let to = to.as_ref();
178 tokio::fs::rename(from, to).await.map_err(|error| {
179 FsError::io(
180 Operation::Write,
181 to,
182 format!("from {}: {error}", from.display()),
183 )
184 })
185}