use midasio::file::{initial_timestamp_unchecked, run_number_unchecked, TryFileViewFromBytesError};
use std::ffi::{OsStr, OsString};
use std::io::Read;
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Clone, Copy, Debug)]
enum Extension {
Mid,
Lz4,
}
#[derive(Debug, Error)]
#[error("unknown conversion from `{extension:?}`")]
pub struct TryExtensionFromOsStrError {
extension: OsString,
}
impl TryFrom<&OsStr> for Extension {
type Error = TryExtensionFromOsStrError;
fn try_from(extension: &OsStr) -> Result<Self, Self::Error> {
match extension.to_str() {
Some("mid") => Ok(Self::Mid),
Some("lz4") => Ok(Self::Lz4),
_ => Err(TryExtensionFromOsStrError {
extension: extension.to_owned(),
}),
}
}
}
#[derive(Debug, Error)]
pub enum AlphaIOError {
#[error("io error")]
IoError(#[from] std::io::Error),
#[error("unknown file extension")]
UnknownExtension(#[from] TryExtensionFromOsStrError),
#[error("midas file format error")]
MidasFileFormatError(#[from] TryFileViewFromBytesError),
#[error("bad run number in `{}` (expected `{expected}`, found `{found}`)", .path.display())]
BadRunNumber {
path: PathBuf,
expected: u32,
found: u32,
},
#[error("duplicate initial timestamp in `{}` and `{}`", .path1.display(), .path2.display())]
DuplicateInitialTimestamp { path1: PathBuf, path2: PathBuf },
}
pub fn read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, AlphaIOError> {
match Extension::try_from(path.as_ref().extension().unwrap_or_default())? {
Extension::Mid => Ok(std::fs::read(&path)?),
Extension::Lz4 => {
let file = std::fs::File::open(&path)?;
let mut decoder = lz4::Decoder::new(file)?;
let mut contents = Vec::new();
std::io::copy(&mut decoder, &mut contents)?;
Ok(contents)
}
}
}
pub fn sort_run_files<P: AsRef<Path>>(
files: impl IntoIterator<Item = P>,
) -> Result<(u32, Vec<P>), AlphaIOError> {
let mut files = files
.into_iter()
.map(|path| {
let mut file = std::fs::File::open(&path)?;
let mut buffer = [0; 12];
match Extension::try_from(path.as_ref().extension().unwrap_or_default())? {
Extension::Mid => {
file.read_exact(&mut buffer)?;
}
Extension::Lz4 => {
let mut decoder = lz4::Decoder::new(&mut file)?;
decoder.read_exact(&mut buffer)?;
}
}
let run_number = run_number_unchecked(&buffer)?;
let initial_timestamp = initial_timestamp_unchecked(&buffer)?;
Ok((run_number, initial_timestamp, path))
})
.collect::<Result<Vec<_>, AlphaIOError>>()?;
assert!(!files.is_empty());
let expected_run_number = files[0].0;
for (run_number, _, path) in &files {
if *run_number != expected_run_number {
return Err(AlphaIOError::BadRunNumber {
path: path.as_ref().to_owned(),
expected: expected_run_number,
found: *run_number,
});
}
}
files.sort_unstable_by_key(|(_, initial_timestamp, _)| *initial_timestamp);
for window in files.windows(2) {
if window[0].1 == window[1].1 {
return Err(AlphaIOError::DuplicateInitialTimestamp {
path1: window[0].2.as_ref().to_owned(),
path2: window[1].2.as_ref().to_owned(),
});
}
}
Ok((
expected_run_number,
files.into_iter().map(|(_, _, path)| path).collect(),
))
}