pub mod fields;
pub mod tracks;
use fields::{field_factory, FieldType, NdeField};
use tracks::{new_column_map, Track};
use parse_display::Display;
use log::{debug, info};
use std::{
convert::TryFrom,
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
path::Path,
};
#[derive(Debug, Display)]
pub enum Cause {
#[display("An error in another crate or module-- cf. source.")]
Other,
#[display("No signature in the index or data file")]
NoSig,
#[display("No indicies found in the index file")]
NoIndicies,
#[display("Failed to read a UTF-8 string")]
NotUtf8,
#[display("Failed to read a UTF-16 string")]
NotUtf16,
#[display("While parsing first record, got field of type {}")]
NonColumnField(FieldType),
#[display("Couldn't interepret {} as a format")]
BadFormat(String),
}
#[derive(Debug, Display)]
#[display("{cause} Source (if any): {source} Stack trace (if any): {trace}")]
pub struct Error {
#[display("XNDE error {}.")]
cause: Cause,
#[display("XNDE error caused by {:#?}.")]
source: Option<Box<dyn std::error::Error>>,
#[display("backtrace: {:#?}.")]
trace: Option<backtrace::Backtrace>,
}
impl Error {
fn new(cause: Cause) -> Error {
Error {
cause: cause,
source: None,
trace: Some(backtrace::Backtrace::new()),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.source {
Some(bx) => Some(bx.as_ref()),
None => None,
}
}
}
impl std::convert::From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error {
cause: Cause::Other,
source: Some(Box::new(err)),
trace: Some(backtrace::Backtrace::new()),
}
}
}
impl std::convert::From<std::string::FromUtf8Error> for Error {
fn from(err: std::string::FromUtf8Error) -> Self {
Error {
cause: Cause::NotUtf8,
source: Some(Box::new(err)),
trace: Some(backtrace::Backtrace::new()),
}
}
}
impl std::convert::From<std::string::FromUtf16Error> for Error {
fn from(err: std::string::FromUtf16Error) -> Self {
Error {
cause: Cause::NotUtf16,
source: Some(Box::new(err)),
trace: Some(backtrace::Backtrace::new()),
}
}
}
impl std::convert::From<fields::Error> for Error {
fn from(err: fields::Error) -> Self {
Error {
cause: Cause::Other,
source: Some(Box::new(err)),
trace: Some(backtrace::Backtrace::new()),
}
}
}
impl std::convert::From<serde_lexpr::error::Error> for Error {
fn from(err: serde_lexpr::error::Error) -> Self {
Error {
cause: Cause::Other,
source: Some(Box::new(err)),
trace: Some(backtrace::Backtrace::new()),
}
}
}
impl std::convert::From<serde_json::error::Error> for Error {
fn from(err: serde_json::error::Error) -> Self {
Error {
cause: Cause::Other,
source: Some(Box::new(err)),
trace: Some(backtrace::Backtrace::new()),
}
}
}
impl std::convert::From<crate::tracks::Error> for Error {
fn from(err: crate::tracks::Error) -> Self {
Error {
cause: Cause::Other,
source: Some(Box::new(err)),
trace: Some(backtrace::Backtrace::new()),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct NdeIndex {
_id: i32,
table: Vec<(u64, i32)>,
}
impl NdeIndex {
fn from_reader<R: std::io::Read>(r: &mut R, nrec: usize) -> Result<Option<NdeIndex>> {
let mut buf: [u8; 4] = [0; 4];
match r.read_exact(&mut buf) {
Err(err) => {
if err.kind() == std::io::ErrorKind::UnexpectedEof {
return Ok(None);
} else {
return Err(Error::from(err));
}
}
_ => (),
}
let id = i32::from_le_bytes(buf);
let mut table: Vec<(u64, i32)> = Vec::with_capacity(nrec);
for _i in 0..nrec {
r.read_exact(&mut buf)?;
let off = u32::from_le_bytes(buf);
r.read_exact(&mut buf)?;
let collab = i32::from_le_bytes(buf);
table.push((off as u64, collab));
}
Ok(Some(NdeIndex {
_id: id,
table: table,
}))
}
fn off(&self, i: usize) -> u64 {
self.table[i].0
}
fn len(&self) -> usize {
self.table.len()
}
}
pub fn read_indicies<R: Read + Seek>(rdr: &mut R) -> Result<Vec<NdeIndex>> {
let mut buf: [u8; 8] = [0; 8];
rdr.read_exact(&mut buf)?;
if b"NDEINDEX" != &buf {
return Err(Error::new(Cause::NoSig));
}
let mut buf: [u8; 4] = [0; 4];
rdr.read_exact(&mut buf)?;
let nrecs = u32::from_le_bytes(buf) as usize;
let mut idxes: Vec<NdeIndex> = Vec::new();
let mut next = NdeIndex::from_reader(rdr, nrecs)?;
while let Some(index) = next {
idxes.push(index);
next = NdeIndex::from_reader(rdr, nrecs)?;
}
Ok(idxes)
}
#[cfg(test)]
mod index_tests {
#[test]
fn smoke() -> Result<(), String> {
use super::*;
let bytes: [u8; 20] = [
0xff, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
];
let idx = match NdeIndex::from_reader(&mut bytes.as_ref(), 2) {
Ok(opt) => match opt {
Some(x) => x,
None => {
return Err(String::from("premature EOF"));
}
},
Err(err) => {
return Err(format!("{}", err));
}
};
assert_eq!(idx.len(), 2);
assert_eq!(idx.off(0), 8);
assert_eq!(idx.off(1), 32);
Ok(())
}
#[test]
fn negative() -> Result<(), String> {
use super::*;
let bytes: [u8; 12] = [
0xff, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let _idx = match NdeIndex::from_reader(&mut bytes.as_ref(), 2) {
Ok(_) => {
return Err(String::from("construction should have failed"));
}
Err(_) => (),
};
Ok(())
}
#[test]
fn idx() -> Result<(), String> {
use super::*;
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(b"NDEINDEX");
buf.extend_from_slice(&2i32.to_le_bytes()); buf.extend_from_slice(&0xffi32.to_le_bytes()); buf.extend_from_slice(&8i32.to_le_bytes()); buf.extend_from_slice(&0i32.to_le_bytes());
buf.extend_from_slice(&0x20i32.to_le_bytes()); buf.extend_from_slice(&1i32.to_le_bytes());
buf.extend_from_slice(&0x00i32.to_le_bytes()); buf.extend_from_slice(&0x20i32.to_le_bytes());
buf.extend_from_slice(&0i32.to_le_bytes());
buf.extend_from_slice(&0x08i32.to_le_bytes());
buf.extend_from_slice(&1i32.to_le_bytes());
let mut cur = std::io::Cursor::new(buf);
match read_indicies(&mut cur) {
Ok(v) => {
assert_eq!(v.len(), 2);
assert_eq!(v[0].len(), 2);
assert_eq!(v[0].off(0), 8);
assert_eq!(v[0].off(1), 32);
assert_eq!(v[1].len(), 2);
assert_eq!(v[1].off(1), 8);
assert_eq!(v[1].off(0), 32);
}
Err(err) => {
return Err(format!("{}", err));
}
}
Ok(())
}
}
fn follow_redirects<R: Read + Seek>(rdr: &mut R) -> Result<(u8, FieldType)> {
let mut id: u8 = 0;
let mut ftype = FieldType::Redirector;
while ftype == FieldType::Redirector {
let mut buf: [u8; 2] = [0; 2];
rdr.read_exact(&mut buf)?;
id = buf[0];
ftype = FieldType::from(buf[1])?;
if ftype == FieldType::Redirector {
let mut buf: [u8; 4] = [0; 4];
rdr.read_exact(&mut buf)?;
let at = u32::from_le_bytes(buf) as u64;
rdr.seek(SeekFrom::Start(at))?;
debug!("found redirect, jumping to {:#04x}", at);
}
}
Ok((id, ftype))
}
#[cfg(test)]
mod redirect_tests {
#[test]
fn smoke() -> Result<(), String> {
use super::*;
let bytes: [u8; 2] = [0x01, 0x00];
let mut rdr = std::io::Cursor::new(bytes);
match follow_redirects(&mut rdr) {
Ok((id, ft)) => {
assert_eq!(id, 1);
assert_eq!(ft, FieldType::Column);
}
Err(err) => {
return Err(format!("{}", err));
}
}
Ok(())
}
}
#[derive(Debug)]
pub enum DumpFormat {
Display,
Sexp,
Json,
}
impl TryFrom<&str> for DumpFormat {
type Error = Error;
fn try_from(x: &str) -> std::result::Result<DumpFormat, Error> {
match x {
"display" => Ok(DumpFormat::Display),
"sexp" => Ok(DumpFormat::Sexp),
"json" => Ok(DumpFormat::Json),
_ => Err(Error::new(Cause::BadFormat(String::from(x)))),
}
}
}
pub fn dump(idx: &Path, dat: &Path, format: DumpFormat) -> Result<()> {
let fdidx = File::open(idx)?;
let mut bufidx = BufReader::new(fdidx);
let idxes = read_indicies(&mut bufidx)?;
info!("There are {} indicies.", idxes.len());
if idxes.len() == 0 {
return Err(Error::new(Cause::NoIndicies));
}
let nrecs = idxes[0].len();
info!("Each index has {} records.", nrecs);
let mut fddat = File::open(dat)?;
let mut buf: [u8; 8] = [0; 8];
fddat.read_exact(&mut buf)?;
if b"NDETABLE" != &buf {
return Err(Error::new(Cause::NoSig));
}
for i in 0..nrecs {
let at = idxes[0].off(i);
debug!("Parsing record {} at {:#04x}.", i, at);
fddat.seek(SeekFrom::Start(at))?;
let mut next_field_pos: u64 = at;
while next_field_pos != 0 {
let (id, ftype) = follow_redirects(&mut fddat)?;
match field_factory(&mut fddat, id as i32, ftype) {
Ok(x) => {
match format {
DumpFormat::Display => info!("{}", x),
DumpFormat::Sexp => info!("{}", serde_lexpr::to_string(&x)?),
DumpFormat::Json => info!("{}", serde_json::to_string(&x)?),
}
next_field_pos = x.next_field_pos();
}
Err(err) => {
return Err(Error::from(err));
}
}
if next_field_pos != 0 {
fddat.seek(SeekFrom::Start(next_field_pos))?;
}
}
}
Ok(())
}
pub enum ExportFormat {
Json,
Sexp,
}
impl TryFrom<&str> for ExportFormat {
type Error = Error;
fn try_from(x: &str) -> std::result::Result<Self, Error> {
match x {
"sexp" => Ok(ExportFormat::Sexp),
"json" => Ok(ExportFormat::Json),
_ => Err(Error::new(Cause::BadFormat(String::from(x)))),
}
}
}
pub fn export(idx: &Path, dat: &Path, format: ExportFormat, out: &Path) -> Result<()> {
let fdidx = File::open(idx)?;
let mut bufidx = BufReader::new(fdidx);
let idxes = read_indicies(&mut bufidx)?;
debug!("There are {} indicies.", idxes.len());
if idxes.len() == 0 {
return Err(Error::new(Cause::NoIndicies));
}
let nrecs = idxes[0].len();
debug!("Each index has {} records.", nrecs);
let mut fddat = File::open(dat)?;
let mut buf: [u8; 8] = [0; 8];
fddat.read_exact(&mut buf)?;
if b"NDETABLE" != &buf {
return Err(Error::new(Cause::NoSig));
}
let at = idxes[0].off(0);
fddat.seek(SeekFrom::Start(at))?;
let mut cols: Vec<fields::ColumnField> = Vec::new();
let mut next_field_pos: u64 = at;
while next_field_pos != 0 {
let (id, ftype) = follow_redirects(&mut fddat)?;
if ftype != FieldType::Column {
return Err(Error::new(Cause::NonColumnField(ftype)));
}
let x = fields::ColumnField::new(&mut fddat, id as i32)?;
next_field_pos = x.next_field_pos();
cols.push(x);
if next_field_pos != 0 {
fddat.seek(SeekFrom::Start(next_field_pos))?;
}
}
debug!("There are {} columns.", cols.len());
let col_map = new_column_map(cols.iter());
debug!("column map: {:#?}", col_map);
let mut trks: Vec<tracks::Track> = Vec::with_capacity(nrecs);
info!("Creating {} Tracks...", nrecs - 2);
for i in 2..nrecs {
let at = idxes[0].off(i);
fddat.seek(SeekFrom::Start(at))?;
let mut rec: Vec<Box<dyn fields::NdeField>> = Vec::with_capacity(cols.len());
let mut next_field_pos: u64 = at;
while next_field_pos != 0 {
let (id, ftype) = follow_redirects(&mut fddat)?;
match field_factory(&mut fddat, id as i32, ftype) {
Ok(x) => {
next_field_pos = x.next_field_pos();
rec.push(x);
}
Err(err) => {
return Err(Error::from(err));
}
}
if next_field_pos != 0 {
fddat.seek(SeekFrom::Start(next_field_pos))?;
}
}
let t = Track::new(&col_map, rec.iter())?;
trks.push(t);
}
info!("Creating {} Tracks...done.", nrecs - 2);
info!("Writing {}...", out.display());
let f = File::create(out)?;
match format {
ExportFormat::Sexp => serde_lexpr::to_writer(f, &trks)?,
ExportFormat::Json => serde_json::to_writer(f, &trks)?,
}
info!("Writing {}...done.", out.display());
Ok(())
}