1use crate::{ProgressEntry, ProgressListener, Pusher};
4use bytes::Bytes;
5use std::{
6 fs::File,
7 io::{Seek, Write},
8};
9use tokio::io::SeekFrom;
10
11pub struct StdFilePusher {
23 file: File,
24 p: u64,
25 sync_all: bool,
26 listener: Option<ProgressListener>,
27}
28
29impl std::fmt::Debug for StdFilePusher {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 f.debug_struct("StdFilePusher")
32 .field("p", &self.p)
33 .field("sync_all", &self.sync_all)
34 .finish_non_exhaustive()
35 }
36}
37
38impl StdFilePusher {
39 pub async fn new(file: tokio::fs::File, size: u64, sync_all: bool) -> std::io::Result<Self> {
42 file.set_len(size).await?;
43 Ok(Self {
44 file: file.into_std().await,
45 p: 0,
46 sync_all,
47 listener: None,
48 })
49 }
50
51 fn write_at(&mut self, start: u64, mut bytes: &[u8]) -> std::io::Result<()> {
54 if self.p != start {
55 if let Err(e) = self.file.seek(SeekFrom::Start(start)) {
56 self.p = u64::MAX;
57 return Err(e);
58 }
59 self.p = start;
60 }
61 while !bytes.is_empty() {
62 match self.file.write(bytes) {
63 Ok(0) => {
64 return Err(std::io::Error::new(
65 std::io::ErrorKind::WriteZero,
66 "failed to write any data",
67 ));
68 }
69 Ok(n) => {
70 let old = self.p;
71 self.p += n as u64;
72 if let Some(l) = &mut self.listener {
73 l(old..self.p);
74 }
75 bytes = &bytes[n..];
76 }
77 Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
78 Err(e) => return Err(e),
79 }
80 }
81 Ok(())
82 }
83}
84
85impl Pusher for StdFilePusher {
86 type Error = std::io::Error;
87
88 fn set_listener(&mut self, cb: ProgressListener) {
89 self.listener = Some(cb);
90 }
91
92 fn push(&mut self, range: &ProgressEntry, bytes: Bytes) -> Result<(), (Self::Error, Bytes)> {
93 if bytes.is_empty() {
94 return Ok(());
95 }
96 let start = range.start;
97 if let Err(e) = self.write_at(start, &bytes) {
98 #[allow(clippy::cast_possible_truncation)]
99 let written_len = if self.p >= start
100 && let offset = (self.p - start) as usize
101 && offset <= bytes.len()
102 {
103 offset
104 } else {
105 0
106 };
107 self.p = u64::MAX;
108 let remaining_bytes = if written_len < bytes.len() {
109 bytes.slice(written_len..)
110 } else {
111 Bytes::new()
112 };
113 return Err((e, remaining_bytes));
114 }
115 Ok(())
116 }
117
118 fn flush(&mut self) -> Result<(), Self::Error> {
119 if self.sync_all {
120 self.file.sync_all()?;
121 }
122 Ok(())
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 #![allow(clippy::unwrap_used)]
129 use super::*;
130 use crate::ProgressEntry;
131 use std::sync::{Arc, Mutex};
132 use std::{io::Read, vec::Vec};
133 use tempfile::NamedTempFile;
134
135 #[tokio::test]
136 async fn test_rand_file_pusher() {
137 let temp_file = NamedTempFile::new().unwrap();
139 let file_path = temp_file.path();
140
141 let mut pusher = StdFilePusher::new(temp_file.reopen().unwrap().into(), 10, false)
143 .await
144 .unwrap();
145
146 let data = b"234";
148 let range = 2..5;
149 pusher.push(&range, data[..].into()).unwrap();
150 pusher.flush().unwrap();
151
152 let mut file_content = Vec::new();
154 File::open(file_path)
155 .unwrap()
156 .read_to_end(&mut file_content)
157 .unwrap();
158 assert_eq!(file_content, b"\0\x00234\0\0\0\0\0");
159 }
160
161 #[tokio::test]
162 async fn test_debug_impl() {
163 let temp_file = NamedTempFile::new().unwrap();
165 let pusher = StdFilePusher::new(temp_file.reopen().unwrap().into(), 10, false)
166 .await
167 .unwrap();
168 let _ = format!("{pusher:?}");
169 }
170
171 #[tokio::test]
172 async fn test_noncontiguous_write_seeks_and_calls_listener() {
173 let temp_file = NamedTempFile::new().unwrap();
176 let mut pusher = StdFilePusher::new(temp_file.reopen().unwrap().into(), 10, false)
177 .await
178 .unwrap();
179 let seen = Arc::new(Mutex::new(None::<ProgressEntry>));
180 let seen2 = seen.clone();
181 pusher.set_listener(Box::new(move |r| {
182 *seen2.lock().unwrap() = Some(r);
183 }));
184 pusher.push(&(5..8), b"xyz"[..].into()).unwrap();
186 assert_eq!(*seen.lock().unwrap(), Some(5..8));
187 }
188
189 #[tokio::test]
190 async fn test_empty_push_is_noop() {
191 let temp_file = NamedTempFile::new().unwrap();
193 let mut pusher = StdFilePusher::new(temp_file.reopen().unwrap().into(), 10, false)
194 .await
195 .unwrap();
196 pusher.push(&(0..0), Bytes::new()).unwrap();
197 }
198
199 #[tokio::test]
200 async fn test_sync_all_flush() {
201 let temp_file = NamedTempFile::new().unwrap();
203 let mut pusher = StdFilePusher::new(temp_file.reopen().unwrap().into(), 10, true)
204 .await
205 .unwrap();
206 pusher.push(&(2..5), b"234"[..].into()).unwrap();
207 pusher.flush().unwrap();
208 }
209}