1use std::path::Path;
4
5use tokio::io::AsyncReadExt;
6
7use crate::{AsyncFile, FsError, Operation};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct FileMetadata {
12 pub length: u64,
14 pub is_directory: bool,
16}
17
18pub async fn read_bounded(path: impl AsRef<Path>, max_bytes: usize) -> Result<Vec<u8>, FsError> {
24 let path = path.as_ref();
25 let file = AsyncFile::open(path).await?;
26 let read_limit = max_bytes
27 .checked_add(1)
28 .ok_or_else(|| FsError::InvalidRequest("read bound overflow".to_owned()))?;
29 let mut output = Vec::with_capacity(max_bytes.min(64 * 1024));
31 file.take(read_limit as u64)
32 .read_to_end(&mut output)
33 .await
34 .map_err(|error| FsError::io(Operation::Read, path, error))?;
35 if output.len() > max_bytes {
36 return Err(FsError::InvalidRequest(format!(
37 "file {} exceeds the {} byte read bound",
38 path.display(),
39 max_bytes
40 )));
41 }
42 Ok(output)
43}
44
45pub async fn read_string_bounded(
47 path: impl AsRef<Path>,
48 max_bytes: usize,
49) -> Result<String, FsError> {
50 let path = path.as_ref();
51 let bytes = read_bounded(path, max_bytes).await?;
52 String::from_utf8(bytes).map_err(|error| {
53 FsError::io(
54 Operation::Read,
55 path,
56 format!("file is not valid UTF-8: {error}"),
57 )
58 })
59}
60
61pub async fn read_string_bounded_if_exists(
64 path: impl AsRef<Path>,
65 max_bytes: usize,
66) -> Result<Option<String>, FsError> {
67 let path = path.as_ref();
68 let Some(file) = AsyncFile::open_if_exists(path).await? else {
69 return Ok(None);
70 };
71 let read_limit = max_bytes
72 .checked_add(1)
73 .ok_or_else(|| FsError::InvalidRequest("read bound overflow".to_owned()))?;
74 let mut output = Vec::with_capacity(max_bytes.min(64 * 1024));
76 file.take(read_limit as u64)
77 .read_to_end(&mut output)
78 .await
79 .map_err(|error| FsError::io(Operation::Read, path, error))?;
80 if output.len() > max_bytes {
81 return Err(FsError::InvalidRequest(format!(
82 "file {} exceeds the {} byte read bound",
83 path.display(),
84 max_bytes
85 )));
86 }
87 String::from_utf8(output)
88 .map(Some)
89 .map_err(|error| FsError::io(Operation::Read, path, error))
90}
91
92pub async fn metadata(path: impl AsRef<Path>) -> Result<FileMetadata, FsError> {
94 let path = path.as_ref();
95 let metadata = tokio::fs::metadata(path)
96 .await
97 .map_err(|error| FsError::io(Operation::Read, path, error))?;
98 Ok(FileMetadata {
99 length: metadata.len(),
100 is_directory: metadata.is_dir(),
101 })
102}
103
104pub async fn symlink_metadata(path: impl AsRef<Path>) -> Result<std::fs::Metadata, FsError> {
106 let path = path.as_ref();
107 tokio::fs::symlink_metadata(path)
108 .await
109 .map_err(|error| FsError::io(Operation::Read, path, error))
110}
111
112pub async fn try_exists(path: impl AsRef<Path>) -> Result<bool, FsError> {
114 let path = path.as_ref();
115 match tokio::fs::metadata(path).await {
116 Ok(_) => Ok(true),
117 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
118 Err(error) => Err(FsError::io(Operation::Read, path, error)),
119 }
120}
121
122pub async fn write_bytes(path: impl AsRef<Path>, bytes: &[u8]) -> Result<(), FsError> {
124 let path = path.as_ref();
125 let mut file = AsyncFile::create(path).await?;
126 file.write_all(bytes).await?;
127 file.flush().await
128}
129
130pub async fn remove_if_exists(path: impl AsRef<Path>) -> Result<(), FsError> {
132 let path = path.as_ref();
133 match tokio::fs::remove_file(path).await {
134 Ok(()) => Ok(()),
135 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
136 Err(error) => Err(FsError::io(Operation::Write, path, error)),
137 }
138}
139
140pub async fn remove_file(path: impl AsRef<Path>) -> Result<(), FsError> {
142 let path = path.as_ref();
143 tokio::fs::remove_file(path)
144 .await
145 .map_err(|error| FsError::io(Operation::Write, path, error))
146}
147
148pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<(), FsError> {
150 let from = from.as_ref();
151 let to = to.as_ref();
152 tokio::fs::rename(from, to).await.map_err(|error| {
153 FsError::io(
154 Operation::Write,
155 to,
156 format!("from {}: {error}", from.display()),
157 )
158 })
159}