1use std::{
39 fmt::{self, Display, Formatter},
40 fs::File,
41 io,
42 path::{Path, PathBuf},
43 result,
44};
45
46#[derive(Debug)]
50pub struct BoxError(Box<dyn std::error::Error + Send + Sync + 'static>);
51
52impl Display for BoxError {
53 fn fmt(&self, f: &mut Formatter<'_>) -> result::Result<(), fmt::Error> {
54 self.0.fmt(f)
55 }
56}
57
58impl std::error::Error for BoxError {
59 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
60 self.0.source()
61 }
62}
63
64#[derive(Debug)]
66pub enum Error {
67 InvalidFinalPath,
69 InvalidParentFinalPath,
71 Io(io::Error),
73 Other(BoxError),
75}
76
77impl Display for Error {
78 fn fmt(&self, f: &mut Formatter<'_>) -> result::Result<(), fmt::Error> {
79 match self {
80 Error::InvalidFinalPath => write!(f, "invalid final path"),
81 Error::InvalidParentFinalPath => write!(f, "invalid parent final path"),
82 Error::Io(e) => e.fmt(f),
83 Error::Other(e) => e.fmt(f),
84 }
85 }
86}
87
88impl std::error::Error for Error {
89 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
90 match self {
91 Error::InvalidFinalPath | Error::InvalidParentFinalPath => None,
92 Error::Io(e) => Some(e),
93 Error::Other(e) => Some(e),
94 }
95 }
96}
97
98impl From<io::Error> for Error {
99 fn from(error: io::Error) -> Self {
100 Error::Io(error)
101 }
102}
103
104#[derive(Debug)]
105struct TempFilePath(PathBuf);
106
107#[derive(Debug)]
108struct FinalPath(PathBuf);
109
110fn final_path_parent(final_path: &Path) -> Result<&Path, Error> {
111 final_path
112 .parent()
113 .filter(|parent| !parent.as_os_str().is_empty())
114 .ok_or(Error::InvalidParentFinalPath)
115}
116
117#[derive(Debug)]
118enum State {
119 Staged {
120 temp_file: File,
121 temp_dir: tempfile::TempDir,
122 temp_file_path: TempFilePath,
123 },
124 Committed,
125}
126
127#[derive(Debug)]
129pub struct StagedFile {
130 final_path: FinalPath,
131 state: State,
132}
133
134impl Drop for StagedFile {
135 fn drop(&mut self) {
136 let mut state = State::Committed;
137 std::mem::swap(&mut self.state, &mut state);
138 if let State::Staged {
139 temp_file,
140 temp_dir,
141 temp_file_path: _temp_file_path,
142 } = state
143 {
144 drop(temp_file);
145 drop(temp_dir);
146 }
147 }
148}
149
150impl StagedFile {
151 pub fn with_final_path<P>(final_path: P) -> Result<Self, Error>
170 where
171 P: AsRef<Path>,
172 {
173 Self::with_final_path_and_temp_dir_prefix(final_path, None)
174 }
175
176 pub fn with_final_path_and_temp_dir_prefix<P>(
198 final_path: P,
199 temp_dir_prefix: Option<&str>,
200 ) -> Result<Self, Error>
201 where
202 P: AsRef<Path>,
203 {
204 let final_path = final_path.as_ref();
205 if final_path.is_dir() {
206 return Err(Error::InvalidFinalPath);
207 }
208 let temp_dir = tempfile::Builder::new()
209 .prefix(temp_dir_prefix.unwrap_or(".staged"))
210 .tempdir_in(final_path_parent(final_path)?)?;
211 let temp_file_path = temp_dir
212 .path()
213 .join(final_path.file_name().ok_or(Error::InvalidFinalPath)?);
214 let temp_file = File::create(&temp_file_path)?;
215
216 Ok(Self {
217 final_path: FinalPath(final_path.to_path_buf()),
218 state: State::Staged {
219 temp_file,
220 temp_dir,
221 temp_file_path: TempFilePath(temp_file_path),
222 },
223 })
224 }
225
226 pub fn commit(mut self) -> Result<(), Error> {
239 let mut state = State::Committed;
240 std::mem::swap(&mut self.state, &mut state);
241 if let State::Staged {
242 temp_file,
243 temp_dir,
244 temp_file_path,
245 } = state
246 {
247 temp_file.sync_all()?;
248 drop(temp_file);
250
251 imp::commit(&temp_file_path, &self.final_path)?;
252
253 drop(temp_dir);
254
255 Ok(())
256 } else {
257 unreachable!()
258 }
259 }
260
261 #[inline]
262 fn as_file(&self) -> &File {
263 if let State::Staged { ref temp_file, .. } = self.state {
264 temp_file
265 } else {
266 unreachable!()
267 }
268 }
269}
270
271impl io::Write for StagedFile {
272 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
273 self.as_file().write(buf)
274 }
275
276 fn flush(&mut self) -> io::Result<()> {
277 self.as_file().flush()
278 }
279
280 fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
281 self.as_file().write_vectored(bufs)
282 }
283
284 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
285 self.as_file().write_all(buf)
286 }
287
288 fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
289 self.as_file().write_fmt(fmt)
290 }
291}
292
293impl io::Write for &StagedFile {
294 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
295 self.as_file().write(buf)
296 }
297
298 fn flush(&mut self) -> io::Result<()> {
299 self.as_file().flush()
300 }
301
302 fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
303 self.as_file().write_vectored(bufs)
304 }
305
306 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
307 self.as_file().write_all(buf)
308 }
309
310 fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
311 self.as_file().write_fmt(fmt)
312 }
313}
314
315impl io::Seek for StagedFile {
316 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
317 self.as_file().seek(pos)
318 }
319
320 fn stream_position(&mut self) -> io::Result<u64> {
321 self.as_file().stream_position()
322 }
323}
324
325impl io::Seek for &StagedFile {
326 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
327 self.as_file().seek(pos)
328 }
329
330 fn stream_position(&mut self) -> io::Result<u64> {
331 self.as_file().stream_position()
332 }
333}
334
335impl io::Read for StagedFile {
336 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
337 self.as_file().read(buf)
338 }
339
340 fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
341 self.as_file().read_vectored(bufs)
342 }
343
344 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
345 self.as_file().read_to_end(buf)
346 }
347
348 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
349 self.as_file().read_to_string(buf)
350 }
351
352 fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
353 self.as_file().read_exact(buf)
354 }
355}
356
357impl io::Read for &StagedFile {
358 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
359 self.as_file().read(buf)
360 }
361
362 fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
363 self.as_file().read_vectored(bufs)
364 }
365
366 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
367 self.as_file().read_to_end(buf)
368 }
369
370 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
371 self.as_file().read_to_string(buf)
372 }
373
374 fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
375 self.as_file().read_exact(buf)
376 }
377}
378
379pub(crate) mod imp;
380
381#[cfg(test)]
382mod test {
383 use super::*;
384
385 #[test]
386 fn commit_staged_file() {
387 use std::io::prelude::*;
388 use std::io::LineWriter;
389
390 let temp_dir = tempfile::tempdir().unwrap();
391 let final_path = temp_dir.path().join("test1");
392 let staged_file = StagedFile::with_final_path(&final_path).unwrap();
393
394 let text = b"Hello World!";
395
396 {
397 let mut line_writer = LineWriter::new(&staged_file);
398 line_writer.write_all(text).unwrap();
399 line_writer.flush().unwrap();
400 }
401
402 staged_file.commit().unwrap();
403
404 assert!(final_path.exists());
405 assert_eq!(std::fs::read(final_path).unwrap(), text);
406 }
407
408 #[test]
409 fn no_commit_staged_file() {
410 use std::io::prelude::*;
411 use std::io::LineWriter;
412
413 let temp_dir = tempfile::tempdir().unwrap();
414 let final_path = temp_dir.path().join("test2");
415 let staged_file = StagedFile::with_final_path(&final_path).unwrap();
416
417 let text = b"Hello World!";
418
419 let mut line_writer = LineWriter::new(&staged_file);
420 line_writer.write_all(text).unwrap();
421 line_writer.flush().unwrap();
422
423 assert!(!final_path.exists());
424 }
425}