1use std::fs::File;
16use std::path::Path;
17
18use bytes::Bytes;
19
20use crate::error::{Error, Result};
21use crate::source::ByteSource;
22
23pub const WRITE_BUFFER_SIZE: usize = 1 << 20;
26
27pub trait ByteSink: Send {
28 fn path(&self) -> &str;
29
30 fn append(&mut self, data: &[u8]) -> Result<u64>;
32
33 fn write_all_at(&mut self, offset: u64, data: &[u8]) -> Result<()>;
35
36 fn position(&self) -> u64;
38
39 fn flush(&mut self) -> Result<()>;
40
41 fn close(&mut self) -> Result<()>;
45}
46
47#[derive(Debug)]
50pub struct LocalSink {
51 file: Option<File>,
53 path: String,
54 stage: Vec<u8>,
59 stage_offset: u64,
60 position: u64,
61}
62
63impl LocalSink {
64 pub fn create(path: impl AsRef<Path>) -> Result<Self> {
65 let path = path.as_ref();
66 let display = path.to_string_lossy().into_owned();
67 let file = File::options()
68 .read(true)
72 .write(true)
73 .create(true)
74 .truncate(true)
75 .open(path)
76 .map_err(|e| Error::io(&display, e))?;
77 Ok(Self {
78 file: Some(file),
79 path: display,
80 stage: Vec::with_capacity(WRITE_BUFFER_SIZE),
81 stage_offset: 0,
82 position: 0,
83 })
84 }
85
86 pub fn discard(&mut self) {
99 self.file = None;
100 self.stage.clear();
101 let _ = std::fs::remove_file(&self.path);
102 }
103
104 pub fn as_source(&mut self) -> Result<SinkSource<'_>> {
107 self.flush()?;
108 let path = self.path.clone();
109 let file = self.file.as_ref().ok_or(Error::Closed { path })?;
110 Ok(SinkSource {
111 file,
112 path: &self.path,
113 len: self.position,
114 })
115 }
116
117 fn handle(&self) -> Result<&File> {
118 self.file.as_ref().ok_or_else(|| Error::Closed {
119 path: self.path.clone(),
120 })
121 }
122
123 fn spill(&mut self, what: &str) -> Result<()> {
129 if self.stage.is_empty() {
130 return Ok(());
131 }
132 let offset = self.stage_offset;
133 {
134 let file = self.handle()?;
135 pwrite(file, offset, &self.stage)
140 .map_err(|e| Error::write(&self.path, format!("{what}: {e}")))?;
141 }
142 self.stage_offset += self.stage.len() as u64;
143 self.stage.clear();
144 Ok(())
145 }
146}
147
148impl ByteSink for LocalSink {
149 fn path(&self) -> &str {
150 &self.path
151 }
152
153 fn append(&mut self, data: &[u8]) -> Result<u64> {
154 let offset = self.position;
155 self.stage.extend_from_slice(data);
156 self.position += data.len() as u64;
157 if self.stage.len() >= WRITE_BUFFER_SIZE {
158 self.spill("the device reported an error while writing to it")?;
159 }
160 Ok(offset)
161 }
162
163 fn write_all_at(&mut self, offset: u64, data: &[u8]) -> Result<()> {
164 self.spill("the device reported an error while writing to it")?;
168 let file = self.handle()?;
169 pwrite(file, offset, data).map_err(|e| {
170 Error::write(
171 &self.path,
172 format!("the device reported an error while writing to it: {e}"),
173 )
174 })?;
175 self.position = self.position.max(offset + data.len() as u64);
180 self.stage_offset = self.stage_offset.max(self.position);
181 Ok(())
182 }
183
184 fn position(&self) -> u64 {
185 self.position
186 }
187
188 fn flush(&mut self) -> Result<()> {
189 self.spill("the device reported an error while flushing it")?;
190 Ok(())
194 }
195
196 fn close(&mut self) -> Result<()> {
197 if self.file.is_none() {
198 return Ok(());
199 }
200 self.spill("the device reported an error while closing it, so the file is incomplete")?;
201 self.file = None;
205 Ok(())
206 }
207}
208
209impl Drop for LocalSink {
210 fn drop(&mut self) {
211 let _ = self.close();
212 }
213}
214
215#[derive(Debug)]
222pub struct SinkSource<'a> {
223 file: &'a File,
224 path: &'a str,
225 len: u64,
226}
227
228impl ByteSource for SinkSource<'_> {
229 fn path(&self) -> &str {
230 self.path
231 }
232
233 fn len(&self) -> Result<u64> {
234 Ok(self.len)
235 }
236
237 fn read_at(&self, offset: u64, len: usize) -> Result<Bytes> {
238 if len == 0 || offset >= self.len {
239 return Ok(Bytes::new());
240 }
241 let len = len.min((self.len - offset) as usize);
242 let mut buf = vec![0u8; len];
243 let read = pread(self.file, offset, &mut buf).map_err(|e| Error::io(self.path, e))?;
244 buf.truncate(read);
245 Ok(Bytes::from(buf))
246 }
247
248 fn close(&self) {
249 }
251}
252
253#[cfg(unix)]
257fn pwrite(file: &File, offset: u64, buf: &[u8]) -> std::io::Result<()> {
258 use std::os::unix::fs::FileExt;
259 let mut written = 0;
260 while written < buf.len() {
261 match file.write_at(&buf[written..], offset + written as u64) {
262 Ok(0) => {
263 return Err(std::io::Error::new(
264 std::io::ErrorKind::WriteZero,
265 "write returned zero bytes",
266 ))
267 }
268 Ok(n) => written += n,
269 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
270 Err(e) => return Err(e),
271 }
272 }
273 Ok(())
274}
275
276#[cfg(windows)]
277fn pwrite(file: &File, offset: u64, buf: &[u8]) -> std::io::Result<()> {
278 use std::os::windows::fs::FileExt;
279 let mut written = 0;
280 while written < buf.len() {
281 match file.seek_write(&buf[written..], offset + written as u64) {
282 Ok(0) => {
283 return Err(std::io::Error::new(
284 std::io::ErrorKind::WriteZero,
285 "write returned zero bytes",
286 ))
287 }
288 Ok(n) => written += n,
289 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
290 Err(e) => return Err(e),
291 }
292 }
293 Ok(())
294}
295
296#[cfg(unix)]
297fn pread(file: &File, offset: u64, buf: &mut [u8]) -> std::io::Result<usize> {
298 use std::os::unix::fs::FileExt;
299 let mut filled = 0;
300 while filled < buf.len() {
301 match file.read_at(&mut buf[filled..], offset + filled as u64) {
302 Ok(0) => break,
303 Ok(n) => filled += n,
304 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
305 Err(e) => return Err(e),
306 }
307 }
308 Ok(filled)
309}
310
311#[cfg(windows)]
312fn pread(file: &File, offset: u64, buf: &mut [u8]) -> std::io::Result<usize> {
313 use std::os::windows::fs::FileExt;
314 let mut filled = 0;
315 while filled < buf.len() {
316 match file.seek_read(&mut buf[filled..], offset + filled as u64) {
317 Ok(0) => break,
318 Ok(n) => filled += n,
319 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
320 Err(e) => return Err(e),
321 }
322 }
323 Ok(filled)
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 fn temp(name: &str) -> std::path::PathBuf {
331 let dir = std::env::temp_dir().join("gwseq_sink_tests");
332 std::fs::create_dir_all(&dir).unwrap();
333 dir.join(name)
334 }
335
336 #[test]
337 fn appends_land_end_to_end_and_report_their_offsets() {
338 let path = temp("append.bin");
339 let mut sink = LocalSink::create(&path).unwrap();
340 assert_eq!(sink.append(b"hello").unwrap(), 0);
341 assert_eq!(sink.append(b" world").unwrap(), 5);
342 assert_eq!(sink.position(), 11);
343 sink.close().unwrap();
344 assert_eq!(std::fs::read(&path).unwrap(), b"hello world");
345 std::fs::remove_file(&path).ok();
346 }
347
348 #[test]
349 fn a_patch_overwrites_staged_bytes_rather_than_racing_them() {
350 let path = temp("patch.bin");
354 let mut sink = LocalSink::create(&path).unwrap();
355 sink.append(&[0u8; 16]).unwrap();
356 sink.append(b"tail").unwrap();
357 sink.write_all_at(4, b"ABCD").unwrap();
358 sink.close().unwrap();
359 let bytes = std::fs::read(&path).unwrap();
360 assert_eq!(&bytes[0..4], &[0, 0, 0, 0]);
361 assert_eq!(&bytes[4..8], b"ABCD");
362 assert_eq!(&bytes[16..20], b"tail");
363 std::fs::remove_file(&path).ok();
364 }
365
366 #[test]
367 fn the_read_view_sees_what_has_only_been_staged() {
368 let path = temp("readback.bin");
371 let mut sink = LocalSink::create(&path).unwrap();
372 sink.append(b"0123456789").unwrap();
373 {
374 let source = sink.as_source().unwrap();
375 assert_eq!(source.len().unwrap(), 10);
376 assert_eq!(&source.read_at(3, 4).unwrap()[..], b"3456");
377 }
378 assert_eq!(sink.append(b"abc").unwrap(), 10);
380 sink.close().unwrap();
381 assert_eq!(std::fs::read(&path).unwrap(), b"0123456789abc");
382 std::fs::remove_file(&path).ok();
383 }
384
385 #[test]
386 fn a_write_larger_than_the_buffer_still_lands_whole() {
387 let path = temp("large.bin");
388 let mut sink = LocalSink::create(&path).unwrap();
389 let big = vec![7u8; WRITE_BUFFER_SIZE + 1234];
390 sink.append(&big).unwrap();
391 sink.append(b"end").unwrap();
392 sink.close().unwrap();
393 let bytes = std::fs::read(&path).unwrap();
394 assert_eq!(bytes.len(), big.len() + 3);
395 assert!(bytes[..big.len()].iter().all(|b| *b == 7));
396 assert_eq!(&bytes[big.len()..], b"end");
397 std::fs::remove_file(&path).ok();
398 }
399
400 #[test]
401 fn closing_twice_is_harmless_and_writing_after_it_is_not() {
402 let path = temp("closed.bin");
403 let mut sink = LocalSink::create(&path).unwrap();
404 sink.append(b"x").unwrap();
405 sink.close().unwrap();
406 sink.close().unwrap();
407 let err = sink.write_all_at(0, b"y").unwrap_err();
409 assert!(matches!(err, Error::Closed { .. }), "{err}");
410 std::fs::remove_file(&path).ok();
411 }
412
413 #[test]
414 fn a_truncating_create_leaves_no_tail_of_an_older_file() {
415 let path = temp("truncate.bin");
416 std::fs::write(&path, vec![9u8; 4096]).unwrap();
417 let mut sink = LocalSink::create(&path).unwrap();
418 sink.append(b"new").unwrap();
419 sink.close().unwrap();
420 assert_eq!(std::fs::read(&path).unwrap(), b"new");
421 std::fs::remove_file(&path).ok();
422 }
423}