1use std::path::{
4 Path,
5 PathBuf,
6};
7use std::pin::Pin;
8use std::task::{
9 Context,
10 Poll,
11};
12
13use tokio::io::{
14 AsyncRead,
15 ReadBuf,
16};
17
18use crate::{
19 AsyncFile,
20 FsError,
21 Operation,
22};
23
24pub async fn atomic_write<R>(target: impl AsRef<Path>, mut reader: R) -> Result<(), FsError>
27where
28 R: AsyncRead + Unpin,
29{
30 let target = target.as_ref().to_owned();
31 let parent = target.parent().ok_or_else(|| {
32 FsError::InvalidRequest(format!("target has no parent: {}", target.display()))
33 })?;
34 tokio::fs::create_dir_all(parent)
35 .await
36 .map_err(|error| FsError::io(Operation::Directory, parent, error))?;
37
38 let temporary = temporary_sibling(&target);
39 let result = async {
40 let mut file = AsyncFile::create_new(&temporary).await?;
41 tokio::io::copy(&mut reader, &mut file)
42 .await
43 .map_err(|error| FsError::io(Operation::Write, &temporary, error))?;
44 file.flush().await?;
45 tokio::fs::rename(&temporary, &target)
46 .await
47 .map_err(|error| FsError::io(Operation::Write, &target, error))
48 }
49 .await;
50
51 match result {
52 Ok(()) => Ok(()),
53 Err(primary) => {
54 match tokio::fs::remove_file(&temporary).await {
57 Ok(()) => Err(primary),
58 Err(cleanup) => Err(FsError::Write {
59 path: temporary.display().to_string(),
60 detail: format!("{primary}; temporary-file cleanup failed: {cleanup}"),
61 }),
62 }
63 }
64 }
65}
66
67pub async fn atomic_write_string(target: impl AsRef<Path>, value: &str) -> Result<(), FsError> {
69 atomic_write(target, SliceReader::new(value.as_bytes())).await
70}
71
72pub async fn atomic_copy(
74 source: impl AsRef<Path>,
75 target: impl AsRef<Path>,
76) -> Result<(), FsError> {
77 let source = source.as_ref();
78 let file = AsyncFile::open(source).await?;
79 atomic_write(target, file).await
80}
81
82fn temporary_sibling(target: &Path) -> PathBuf {
83 let mut value = target.as_os_str().to_owned();
84 value.push(format!(".tmp.{}", uuid::Uuid::new_v4()));
85 value.into()
86}
87
88struct SliceReader<'a> {
89 bytes: &'a [u8],
90 position: usize,
91}
92
93impl<'a> SliceReader<'a> {
94 fn new(bytes: &'a [u8]) -> Self {
95 Self { bytes, position: 0 }
96 }
97}
98
99impl AsyncRead for SliceReader<'_> {
100 fn poll_read(
101 mut self: Pin<&mut Self>,
102 _context: &mut Context<'_>,
103 buffer: &mut ReadBuf<'_>,
104 ) -> Poll<std::io::Result<()>> {
105 let remaining = &self.bytes[self.position..];
106 if remaining.is_empty() {
107 return Poll::Ready(Ok(()));
108 }
109 let amount = remaining.len().min(buffer.remaining());
110 buffer.put_slice(&remaining[..amount]);
111 self.position += amount;
112 Poll::Ready(Ok(()))
113 }
114}