rubbl_core/io.rs
1// Copyright 2017 Peter Williams
2// Licensed under the MIT License.
3
4/*!
5
6Basic I/O helpers.
7
8 */
9
10use byteorder::{BigEndian, ByteOrder};
11use num_complex::Complex;
12use std::io;
13use std::io::{Read, Result, Write};
14use std::result;
15
16/// This struct wraps a Read type to equip it with hooks to track its
17/// alignment — that is, how many bytes into the stream the read has
18/// progressed, and whether the current offset is an exact multiple of a
19/// certain number of bytes from the beginning.
20///
21/// Streams often have alignment requirements so that they can safely be
22/// mapped into in-memory data structures. In particular, this is the case for
23/// MIRIAD files.
24#[derive(Debug)]
25pub struct AligningReader<R: Read> {
26 inner: R,
27 offset: u64,
28}
29
30impl<R: Read> AligningReader<R> {
31 /// Create a new AligningReader that wraps the argument *inner*.
32 pub fn new(inner: R) -> Self {
33 AligningReader { inner, offset: 0 }
34 }
35
36 /// Consume this struct, returning the underlying inner reader.
37 pub fn into_inner(self) -> R {
38 self.inner
39 }
40
41 /// Return how many bytes we have read since this struct was created.
42 ///
43 /// Note that this offset is tracked internally. If you open a file, read
44 /// part of it, and *then* create an AligningReader, the returned offset
45 /// will refer to the number of bytes read since creation, not the actual
46 /// file position as understood by the underlying OS.
47 pub fn offset(&self) -> u64 {
48 self.offset
49 }
50
51 /// Read and discard bytes to ensure that the stream is aligned as specified.
52 ///
53 /// The maximum allowed alignment value is 64 bytes.
54 ///
55 /// Returns whether the stream was already at the right alignment. When
56 /// that is the case, no read is performed.
57 pub fn align_to(&mut self, alignment: usize) -> Result<bool> {
58 let mut buf = [0u8; 64];
59
60 if alignment > 64 {
61 panic!("maximum alignment size is 64");
62 }
63
64 let excess = (self.offset % alignment as u64) as usize;
65
66 if excess == 0 {
67 Ok(true)
68 } else {
69 let amount = alignment - excess;
70 let result = self.inner.eof_read_exact(&mut buf[..amount]);
71
72 if result.is_ok() {
73 self.offset += amount as u64;
74 }
75
76 result
77 }
78 }
79}
80
81impl<R: Read> Read for AligningReader<R> {
82 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
83 let result = self.inner.read(buf);
84
85 if let Ok(n) = result {
86 self.offset += n as u64;
87 }
88
89 result
90 }
91}
92
93/// In analogoy with AligningReader, this struct wraps a Write type to equip
94/// it with hooks to track its alignment — that is, how many bytes into the
95/// stream the write has progressed, and whether the current offset is an
96/// exact multiple of a certain number of bytes from the beginning.
97///
98/// Streams often have alignment requirements so that they can safely be
99/// mapped into in-memory data structures. In particular, this is the case for
100/// MIRIAD files.
101#[derive(Debug)]
102pub struct AligningWriter<W: Write> {
103 inner: W,
104 offset: u64,
105}
106
107impl<W: Write> AligningWriter<W> {
108 /// Create a new AligningWriter that wraps the argument *inner*.
109 pub fn new(inner: W) -> Self {
110 AligningWriter { inner, offset: 0 }
111 }
112
113 /// Consume this struct, returning the underlying inner writer.
114 pub fn into_inner(self) -> W {
115 self.inner
116 }
117
118 /// Return how many bytes we have written since this struct was created.
119 ///
120 /// Note that this offset is tracked internally. If you open a file, write
121 /// some data, and *then* create an AligningWriter, the returned offset
122 /// will refer to the number of bytes written since creation, not the
123 /// actual file position as understood by the underlying OS.
124 pub fn offset(&self) -> u64 {
125 self.offset
126 }
127
128 /// Write zero bytes to ensure that the stream is aligned as specified.
129 ///
130 /// The maximum allowed alignment value is 64 bytes.
131 ///
132 /// Returns whether the stream was already at the right alignment. When
133 /// that is the case, no write is performed.
134 pub fn align_to(&mut self, alignment: usize) -> Result<bool> {
135 let buf = [0u8; 64];
136
137 if alignment > 64 {
138 panic!("maximum alignment size is 64");
139 }
140
141 let excess = (self.offset % alignment as u64) as usize;
142
143 if excess == 0 {
144 Ok(true)
145 } else {
146 let amount = alignment - excess;
147 self.inner.write_all(&buf[..amount])?;
148 self.offset += amount as u64;
149 Ok(false)
150 }
151 }
152}
153
154impl<W: Write> Write for AligningWriter<W> {
155 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
156 let result = self.inner.write(buf);
157
158 if let Ok(n) = result {
159 self.offset += n as u64;
160 }
161
162 result
163 }
164
165 fn flush(&mut self) -> io::Result<()> {
166 self.inner.flush()
167 }
168}
169
170/// This is an extension trait that makes it more convenient to handle errors
171/// when opening files that may be missing.
172///
173/// Various parts of Rubbl try to open files but don’t mind if the files are
174/// missing. This is expressed using return types like `Result<Option<File>>`:
175/// if the value is `Ok(None)`, that means that the file was not found, even
176/// though the underlying I/O operation probably return an `Err` type.
177///
178/// There are times when we want to use these APIs, but it actually is an
179/// error if the file in question is missing. This trait provides a
180/// `require_found` method on the `Result<Option<T>>` type that removes the
181/// `Option` layer of the type, converting `Ok(None)` into an `Err` containing
182/// a `NotFound` error.
183pub trait OpenResultExt {
184 /// The output type of the `require_found` method.
185 ///
186 /// `Result<Option<T>>` becomes `Result<T>`. Due to the way the trait is
187 /// specified, we have to use an associated type to express this fact.
188 type Reprocessed;
189
190 /// If *self* is `Ok(None)`, convert it into an `Err` with a `NotFound`
191 /// type.
192 fn require_found(self) -> Self::Reprocessed;
193}
194
195impl<T, E> OpenResultExt for result::Result<Option<T>, E>
196where
197 E: From<io::Error>,
198{
199 type Reprocessed = result::Result<T, E>;
200
201 fn require_found(self) -> Self::Reprocessed {
202 match self {
203 Err(e) => Err(e),
204 Ok(o) => {
205 if let Some(x) = o {
206 Ok(x)
207 } else {
208 Err(io::Error::new(io::ErrorKind::NotFound, "not found").into())
209 }
210 }
211 }
212 }
213}
214
215/// Extend the `Read` trait to provide functions for reading an exact number
216/// of bytes from a stream and distinguishing whether EOF was encountered
217/// immediately, versus whether it was encountered in the midst of the read.
218pub trait EofReadExactExt: Read {
219 /// Like `Read::read_exact`, except returns Ok(false) if EOF was
220 /// encountered at the first read attempt. Returns Ok(true) if everything
221 /// was OK and EOF has not yet been hit. Returns Err with an IoError with
222 /// a "kind" of UnexpectedEof if EOF was encountered somewhere in the
223 /// midst of the buffer.
224 fn eof_read_exact<E>(&mut self, buf: &mut [u8]) -> result::Result<bool, E>
225 where
226 E: From<io::Error>;
227
228 /// Like `byteorder::ReadBytesExt::read_i16::<BigEndian>`, except returns
229 /// Some(n) on success and None if EOF was encountered at the first read
230 /// attempt.
231 fn eof_read_be_i16<E>(&mut self) -> result::Result<Option<i16>, E>
232 where
233 E: From<io::Error>,
234 {
235 let mut buf = [0u8; 2];
236
237 if self.eof_read_exact(&mut buf)? {
238 Ok(Some(BigEndian::read_i16(&buf)))
239 } else {
240 Ok(None)
241 }
242 }
243
244 /// Like `byteorder::ReadBytesExt::read_i32::<BigEndian>`, except returns
245 /// Some(n) on success and None if EOF was encountered at the first read
246 /// attempt.
247 fn eof_read_be_i32<E>(&mut self) -> result::Result<Option<i32>, E>
248 where
249 E: From<io::Error>,
250 {
251 let mut buf = [0u8; 4];
252
253 if self.eof_read_exact(&mut buf)? {
254 Ok(Some(BigEndian::read_i32(&buf)))
255 } else {
256 Ok(None)
257 }
258 }
259
260 /// Like `byteorder::ReadBytesExt::read_i64::<BigEndian>`, except returns
261 /// Some(n) on success and None if EOF was encountered at the first read
262 /// attempt.
263 fn eof_read_be_i64<E>(&mut self) -> result::Result<Option<i64>, E>
264 where
265 E: From<io::Error>,
266 {
267 let mut buf = [0u8; 8];
268
269 if self.eof_read_exact(&mut buf)? {
270 Ok(Some(BigEndian::read_i64(&buf)))
271 } else {
272 Ok(None)
273 }
274 }
275
276 /// Like `byteorder::ReadBytesExt::read_f32::<BigEndian>`, except returns
277 /// Some(n) on success and None if EOF was encountered at the first read
278 /// attempt.
279 fn eof_read_be_f32<E>(&mut self) -> result::Result<Option<f32>, E>
280 where
281 E: From<io::Error>,
282 {
283 let mut buf = [0u8; 4];
284
285 if self.eof_read_exact(&mut buf)? {
286 Ok(Some(BigEndian::read_f32(&buf)))
287 } else {
288 Ok(None)
289 }
290 }
291
292 /// Like `byteorder::ReadBytesExt::read_f64::<BigEndian>`, except returns
293 /// Some(n) on success and None if EOF was encountered at the first read
294 /// attempt.
295 fn eof_read_be_f64<E>(&mut self) -> result::Result<Option<f64>, E>
296 where
297 E: From<io::Error>,
298 {
299 let mut buf = [0u8; 4];
300
301 if self.eof_read_exact(&mut buf)? {
302 Ok(Some(BigEndian::read_f64(&buf)))
303 } else {
304 Ok(None)
305 }
306 }
307
308 /// Like `byteorder::ReadBytesExt::read_f32::<BigEndian>`, except it reads
309 /// two values and packs them into a `Complex<f32>`, and returns Some(n)
310 /// on success and None if EOF was encountered at the first read attempt.
311 /// The real part comes before the imaginary part.
312 fn eof_read_be_c64<E>(&mut self) -> result::Result<Option<Complex<f32>>, E>
313 where
314 E: From<io::Error>,
315 {
316 let mut buf = [0u8; 8];
317
318 if self.eof_read_exact(&mut buf)? {
319 Ok(Some(Complex::new(
320 BigEndian::read_f32(&buf[..4]),
321 BigEndian::read_f32(&buf[4..]),
322 )))
323 } else {
324 Ok(None)
325 }
326 }
327}
328
329impl<R: Read> EofReadExactExt for R {
330 fn eof_read_exact<E>(&mut self, buf: &mut [u8]) -> result::Result<bool, E>
331 where
332 E: From<io::Error>,
333 {
334 let mut n_left = buf.len();
335 let mut ofs = 0;
336
337 while n_left > 0 {
338 let n_read = match self.read(&mut buf[ofs..]) {
339 Ok(n) => n,
340 Err(e) => {
341 if e.kind() == io::ErrorKind::Interrupted {
342 continue;
343 }
344
345 return Err(e.into());
346 }
347 };
348
349 if n_read == 0 {
350 return if ofs == 0 {
351 Ok(false) // no more data at an expected stopping point
352 } else {
353 Err(
354 io::Error::new(io::ErrorKind::UnexpectedEof, "unexpected end of file")
355 .into(),
356 )
357 };
358 }
359
360 ofs += n_read;
361 n_left -= n_read;
362 }
363
364 Ok(true) // more data, we think
365 }
366}