use flate2::read::GzDecoder;
use std::fs::File;
use std::io::{BufRead, BufReader, Result, Seek};
use std::path::Path;
pub trait ReadSeek: BufRead + Seek {}
impl<T: BufRead + Seek> ReadSeek for T {}
pub fn open_txt(path: &str) -> Result<BufReader<File>> {
let file = File::open(path)?;
Ok(BufReader::new(file))
}
pub fn open_gz(path: &str) -> Result<BufReader<GzDecoder<File>>> {
let file = File::open(path)?;
let decoder = GzDecoder::new(file);
Ok(BufReader::new(decoder))
}
pub trait Reader {
type R: BufRead;
type Frame;
fn new(reader: Self::R) -> Self;
}
pub trait FrameReader: Reader {
fn read_frame(&mut self) -> Result<Option<Self::Frame>>;
fn read_all(&mut self) -> Result<Vec<Self::Frame>> {
let mut out = Vec::new();
while let Some(frame) = self.read_frame()? {
out.push(frame);
}
Ok(out)
}
}
#[derive(Debug, Clone)]
pub struct FrameIndex {
pub offsets: Vec<u64>,
}
impl FrameIndex {
pub fn new() -> Self {
Self {
offsets: Vec::new(),
}
}
pub fn add_frame(&mut self, offset: u64) {
self.offsets.push(offset);
}
pub fn len(&self) -> usize {
self.offsets.len()
}
pub fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
pub fn get(&self, step: usize) -> Option<u64> {
self.offsets.get(step).copied()
}
}
impl Default for FrameIndex {
fn default() -> Self {
Self::new()
}
}
pub struct FrameIterator<'a, R: TrajReader + ?Sized> {
reader: &'a mut R,
current: usize,
}
impl<'a, R: TrajReader> Iterator for FrameIterator<'a, R> {
type Item = Result<R::Frame>;
fn next(&mut self) -> Option<Self::Item> {
match self.reader.read_step(self.current) {
Ok(Some(frame)) => {
self.current += 1;
Some(Ok(frame))
}
Ok(None) => None,
Err(e) => Some(Err(e)),
}
}
}
pub trait TrajReader: Reader {
fn build_index(&mut self) -> Result<()>;
fn read_step(&mut self, step: usize) -> Result<Option<Self::Frame>>;
fn len(&mut self) -> Result<usize>;
fn is_empty(&mut self) -> Result<bool> {
Ok(self.len()? == 0)
}
fn iter(&mut self) -> FrameIterator<'_, Self>
where
Self: Sized,
{
FrameIterator {
reader: self,
current: 0,
}
}
}
pub fn open_seekable<P: AsRef<Path>>(path: P) -> Result<Box<dyn ReadSeek>> {
let path = path.as_ref();
let file = File::open(path)?;
if path
.extension()
.and_then(|s| s.to_str())
.map(|s| s.eq_ignore_ascii_case("gz"))
.unwrap_or(false)
{
use std::io::Read;
let decoder = GzDecoder::new(file);
let mut content = Vec::new();
let mut buf_decoder = BufReader::new(decoder);
buf_decoder.read_to_end(&mut content)?;
Ok(Box::new(std::io::Cursor::new(content)))
} else {
Ok(Box::new(BufReader::new(file)))
}
}
pub fn open_streaming<P: AsRef<Path>>(path: P) -> Result<Box<dyn BufRead>> {
let path = path.as_ref();
let file = File::open(path)?;
if path
.extension()
.and_then(|s| s.to_str())
.map(|s| s.eq_ignore_ascii_case("gz"))
.unwrap_or(false)
{
let decoder = GzDecoder::new(file);
Ok(Box::new(BufReader::new(decoder)))
} else {
Ok(Box::new(BufReader::new(file)))
}
}
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Box<dyn ReadSeek>> {
open_seekable(path)
}
#[cfg(test)]
mod tests {
use super::{open_seekable, open_streaming};
use flate2::Compression;
use flate2::write::GzEncoder;
use std::io::{BufRead, Write};
use std::path::PathBuf;
fn temp_path(name: &str) -> PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("molrs_reader_test_{}", name));
path
}
#[test]
fn open_seekable_plain_text() {
let path = temp_path("plain.txt");
std::fs::write(&path, b"hello\n").expect("write temp");
let mut reader = open_seekable(&path).expect("open seekable");
let mut line = String::new();
reader.read_line(&mut line).expect("read line");
assert_eq!(line, "hello\n");
let _ = std::fs::remove_file(&path);
}
#[test]
fn open_streaming_gz() {
let path = temp_path("data.txt.gz");
let file = std::fs::File::create(&path).expect("create gz");
let mut encoder = GzEncoder::new(file, Compression::default());
encoder.write_all(b"hello\n").expect("write gz");
encoder.finish().expect("finish gz");
let mut reader = open_streaming(&path).expect("open streaming");
let mut line = String::new();
reader.read_line(&mut line).expect("read line");
assert_eq!(line, "hello\n");
let _ = std::fs::remove_file(&path);
}
}