1#[cfg(unix)]
4mod lock_unix;
5#[cfg(windows)]
6mod lock_windows;
7
8use crate::error::FileError;
9#[cfg(unix)]
10pub use lock_unix::*;
11#[cfg(windows)]
12pub use lock_windows::*;
13use std::{
14 fs::{self, File, OpenOptions, ReadDir},
15 io::{Read, Write},
16 path::{Path, PathBuf},
17};
18use tempfile::NamedTempFile;
19use walkdir::WalkDir;
20
21pub const LOCK_FILE_NAME: &str = ".tmc.lock";
22
23#[derive(Debug, Clone, Copy)]
24pub enum LockOptions {
25 Read,
27 ReadCreate,
29 ReadTruncate,
31 Write,
33 WriteCreate,
35 WriteTruncate,
37}
38
39impl LockOptions {
40 fn into_open_options(self) -> OpenOptions {
41 let mut opts = OpenOptions::new();
42 match self {
43 Self::Read => opts.read(true),
44 Self::ReadCreate => opts.read(true).write(true).create(true),
46 Self::ReadTruncate => opts.write(true).create(true).truncate(true),
48 Self::Write => opts.write(true),
49 Self::WriteCreate => opts.write(true).create(true),
50 Self::WriteTruncate => opts.write(true).create(true).truncate(true),
51 };
52 opts
53 }
54}
55
56pub fn temp_file() -> Result<File, FileError> {
57 tempfile::tempfile().map_err(FileError::TempFile)
58}
59
60pub fn named_temp_file() -> Result<NamedTempFile, FileError> {
61 tempfile::NamedTempFile::new().map_err(FileError::TempFile)
62}
63
64pub fn named_temp_file_in(path: &Path) -> Result<NamedTempFile, FileError> {
65 tempfile::NamedTempFile::new_in(path).map_err(FileError::TempFile)
66}
67
68pub fn open_file(path: impl AsRef<Path>) -> Result<File, FileError> {
69 let path = path.as_ref();
70 File::open(path).map_err(|e| FileError::FileOpen(path.to_path_buf(), e))
71}
72
73pub fn read_reader<R: Read>(mut reader: R) -> Result<Vec<u8>, FileError> {
74 let mut bytes = vec![];
75 reader
76 .read_to_end(&mut bytes)
77 .map_err(FileError::ReadError)?;
78 Ok(bytes)
79}
80
81pub fn read_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, FileError> {
82 let path = path.as_ref();
83 let mut file = open_file(path)?;
84 let mut bytes = vec![];
85 file.read_to_end(&mut bytes)
86 .map_err(|e| FileError::FileRead(path.to_path_buf(), e))?;
87 Ok(bytes)
88}
89
90pub fn read_file_to_string<P: AsRef<Path>>(path: P) -> Result<String, FileError> {
91 let path = path.as_ref();
92 let s = fs::read_to_string(path).map_err(|e| FileError::FileRead(path.to_path_buf(), e))?;
93 Ok(s)
94}
95
96pub fn read_file_to_string_lossy<P: AsRef<Path>>(path: P) -> Result<String, FileError> {
97 let path = path.as_ref();
98 let bytes = read_file(path)?;
99 let s = String::from_utf8_lossy(&bytes).into_owned();
100 Ok(s)
101}
102
103pub fn create_file<P: AsRef<Path>>(path: P) -> Result<File, FileError> {
105 let path = path.as_ref();
106 if let Some(parent) = path.parent() {
107 if !parent.exists() {
108 create_dir_all(parent)?;
109 }
110 }
111 File::create(path).map_err(|e| FileError::FileCreate(path.to_path_buf(), e))
112}
113
114pub fn remove_all<P: AsRef<Path>>(path: P) -> Result<(), FileError> {
116 let path = path.as_ref();
117 if path.is_file() {
118 remove_file(path)
119 } else if path.is_dir() {
120 remove_dir_all(path)
121 } else {
122 Ok(())
123 }
124}
125
126pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<(), FileError> {
127 let path = path.as_ref();
128 fs::remove_file(path).map_err(|e| FileError::FileRemove(path.to_path_buf(), e))
129}
130
131pub fn remove_file_locked<P: AsRef<Path>>(path: P) -> Result<(), FileError> {
132 let path = path.as_ref();
133 let _lock = Lock::file(path, LockOptions::Write)?;
134 fs::remove_file(path).map_err(|e| FileError::FileRemove(path.to_path_buf(), e))
135}
136
137pub fn write_to_file<S: AsRef<[u8]>, P: AsRef<Path>>(
138 source: S,
139 target: P,
140) -> Result<File, FileError> {
141 let target = target.as_ref();
142 let mut target_file = create_file(target)?;
143 target_file
144 .write_all(source.as_ref())
145 .map_err(|e| FileError::FileWrite(target.to_path_buf(), e))?;
146 Ok(target_file)
147}
148
149pub fn write_to_writer<S: AsRef<[u8]>, W: Write>(
150 source: S,
151 mut target: W,
152) -> Result<(), FileError> {
153 target
154 .write_all(source.as_ref())
155 .map_err(FileError::WriteError)?;
156 Ok(())
157}
158
159pub fn read_to_file<R: Read, P: AsRef<Path>>(source: &mut R, target: P) -> Result<File, FileError> {
161 let target = target.as_ref();
162 let mut target_file = create_file(target)?;
163 std::io::copy(source, &mut target_file)
164 .map_err(|e| FileError::FileWrite(target.to_path_buf(), e))?;
165 Ok(target_file)
166}
167
168pub fn read_dir<P: AsRef<Path>>(path: P) -> Result<ReadDir, FileError> {
169 fs::read_dir(&path).map_err(|e| FileError::DirRead(path.as_ref().to_path_buf(), e))
170}
171
172pub fn create_dir<P: AsRef<Path>>(path: P) -> Result<(), FileError> {
173 fs::create_dir(&path).map_err(|e| FileError::DirCreate(path.as_ref().to_path_buf(), e))
174}
175
176pub fn create_dir_all<P: AsRef<Path>>(path: P) -> Result<(), FileError> {
177 fs::create_dir_all(&path).map_err(|e| FileError::DirCreate(path.as_ref().to_path_buf(), e))
178}
179
180pub fn remove_dir_empty<P: AsRef<Path>>(path: P) -> Result<(), FileError> {
181 fs::remove_dir(&path).map_err(|e| FileError::DirRemove(path.as_ref().to_path_buf(), e))
182}
183
184pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> Result<(), FileError> {
185 fs::remove_dir_all(&path).map_err(|e| FileError::DirRemove(path.as_ref().to_path_buf(), e))
186}
187
188pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<(), FileError> {
189 let from = from.as_ref();
190 let to = to.as_ref();
191 fs::rename(from, to).map_err(|e| FileError::Rename {
192 from: from.to_path_buf(),
193 to: to.to_path_buf(),
194 source: e,
195 })
196}
197
198pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(source: P, target: Q) -> Result<(), FileError> {
204 let source = source.as_ref();
205 let target = target.as_ref();
206
207 if source.is_file() {
208 if target.is_dir() {
209 log::trace!(
210 "copying into dir {} -> {}",
211 source.display(),
212 target.display()
213 );
214 let file_name = if let Some(file_name) = source.file_name() {
215 file_name
216 } else {
217 return Err(FileError::NoFileName(source.to_path_buf()));
218 };
219 let path_in_target = target.join(file_name);
220 std::fs::copy(source, path_in_target).map_err(|e| FileError::FileCopy {
221 from: source.to_path_buf(),
222 to: target.to_path_buf(),
223 source: e,
224 })?;
225 } else {
226 log::trace!("copying file {} -> {}", source.display(), target.display());
227 if let Some(parent) = target.parent() {
228 if !parent.exists() {
229 create_dir_all(parent)?;
230 }
231 }
232 std::fs::copy(source, target).map_err(|e| FileError::FileCopy {
233 from: source.to_path_buf(),
234 to: target.to_path_buf(),
235 source: e,
236 })?;
237 }
238 } else {
239 log::trace!(
240 "recursively copying {} -> {}",
241 source.display(),
242 target.display()
243 );
244 if target.is_file() {
245 return Err(FileError::UnexpectedFile(target.to_path_buf()));
246 } else {
247 let prefix = source.parent().unwrap_or_else(|| Path::new(""));
248 for entry in WalkDir::new(source) {
249 let entry = entry?;
250 let entry_path = entry.path();
251 let stripped = entry_path
252 .strip_prefix(prefix)
253 .expect("prefix is derived from the source which entry_path is in");
254
255 let target = target.join(stripped);
256 if entry_path.is_dir() {
257 create_dir_all(target)?;
258 } else {
259 if let Some(parent) = target.parent() {
260 create_dir_all(parent)?;
261 }
262 std::fs::copy(entry_path, &target).map_err(|e| FileError::FileCopy {
263 from: entry_path.to_path_buf(),
264 to: target.clone(),
265 source: e,
266 })?;
267 }
268 }
269 }
270 }
271 Ok(())
272}
273
274pub fn canonicalize(path: &Path) -> Result<PathBuf, FileError> {
275 let canon =
276 dunce::canonicalize(path).map_err(|e| FileError::Canonicalize(path.to_path_buf(), e))?;
277 Ok(canon)
278}
279
280#[cfg(test)]
281#[allow(clippy::unwrap_used)]
282mod test {
283 use super::*;
284 use std::path::PathBuf;
285
286 fn init() {
287 use log::*;
288 use simple_logger::*;
289 let _ = SimpleLogger::new().with_level(LevelFilter::Debug).init();
290 }
291
292 fn file_to(
293 target_dir: impl AsRef<std::path::Path>,
294 target_relative: impl AsRef<std::path::Path>,
295 contents: impl AsRef<[u8]>,
296 ) -> PathBuf {
297 let target = target_dir.as_ref().join(target_relative);
298 if let Some(parent) = target.parent() {
299 std::fs::create_dir_all(parent).unwrap();
300 }
301 std::fs::write(&target, contents.as_ref()).unwrap();
302 target
303 }
304
305 fn dir_to(
306 target_dir: impl AsRef<std::path::Path>,
307 target_relative: impl AsRef<std::path::Path>,
308 ) -> PathBuf {
309 let target = target_dir.as_ref().join(target_relative);
310 std::fs::create_dir_all(&target).unwrap();
311 target
312 }
313
314 #[test]
315 fn copies_file_to_file() {
316 init();
317
318 let temp = tempfile::tempdir().unwrap();
319 file_to(&temp, "dir/file", "file contents");
320
321 let target = tempfile::tempdir().unwrap();
322 copy(
323 temp.path().join("dir/file"),
324 target.path().join("another/place"),
325 )
326 .unwrap();
327
328 let conts = read_file_to_string(target.path().join("another/place")).unwrap();
329 assert_eq!(conts, "file contents");
330 }
331
332 #[test]
333 fn copies_file_to_dir() {
334 init();
335
336 let temp = tempfile::tempdir().unwrap();
337 file_to(&temp, "dir/file", "file contents");
338
339 let target = tempfile::tempdir().unwrap();
340 dir_to(&target, "some/dir");
341 copy(temp.path().join("dir/file"), target.path().join("some/dir")).unwrap();
342
343 let conts = read_file_to_string(target.path().join("some/dir/file")).unwrap();
344 assert_eq!(conts, "file contents");
345 }
346
347 #[test]
348 fn copies_dir() {
349 init();
350 let temp = tempfile::tempdir().unwrap();
351 file_to(&temp, "dir/another/file", "file contents");
352 file_to(&temp, "dir/elsewhere/f", "another file");
353 dir_to(&temp, "dir/some dir");
354
355 let target = tempfile::tempdir().unwrap();
356 copy(temp.path().join("dir"), target.path()).unwrap();
357
358 let conts = read_file_to_string(target.path().join("dir/another/file")).unwrap();
359 assert_eq!(conts, "file contents");
360 let conts = read_file_to_string(target.path().join("dir/elsewhere/f")).unwrap();
361 assert_eq!(conts, "another file");
362 assert!(target.path().join("dir/some dir").is_dir());
363 }
364}