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}