use std::io::{Read, Seek};
use anyhow::Result;
use super::constants::*;
use super::tag::{read_i32, read_tag_header, TagHeader};
#[derive(Debug, Default, Clone)]
pub struct Node {
pub block: i32,
pub entries: Vec<TagHeader>,
pub children: Vec<Node>,
}
impl Node {
pub fn find_block(&self, kind: i32) -> Option<&Node> {
if self.block == kind {
return Some(self);
}
for child in &self.children {
if let Some(found) = child.find_block(kind) {
return Some(found);
}
}
None
}
pub fn find_blocks(&self, kind: i32) -> Vec<&Node> {
let mut out = Vec::new();
self.collect_blocks(kind, &mut out);
out
}
fn collect_blocks<'a>(&'a self, kind: i32, out: &mut Vec<&'a Node>) {
if self.block == kind {
out.push(self);
}
for child in &self.children {
child.collect_blocks(kind, out);
}
}
pub fn find_tag(&self, kind: i32) -> Option<&TagHeader> {
self.entries.iter().find(|e| e.kind == kind)
}
}
pub fn build_tree(directory: &[TagHeader]) -> Node {
let mut stack: Vec<Node> = vec![Node::default()]; for &tag in directory {
match tag.kind {
FIFF_BLOCK_START => {
stack.push(Node::default());
}
FIFF_BLOCK_END => {
let finished = stack.pop().unwrap_or_default();
if let Some(parent) = stack.last_mut() {
parent.children.push(finished);
}
}
_ => {
if let Some(node) = stack.last_mut() {
node.entries.push(tag);
}
}
}
}
while stack.len() > 1 {
let orphan = stack.pop().unwrap();
stack[0].children.push(orphan);
}
stack.pop().unwrap_or_default()
}
pub fn read_tree<R: Read + Seek>(reader: &mut R, directory: &[TagHeader]) -> Result<Node> {
let mut root = Node::default();
build_tree_resolved(reader, directory, &mut root)?;
Ok(root)
}
fn build_tree_resolved<R: Read + Seek>(
reader: &mut R,
directory: &[TagHeader],
root: &mut Node,
) -> Result<()> {
let mut stack: Vec<Node> = vec![Node {
block: 0, entries: Vec::new(),
children: Vec::new(),
}];
for &tag in directory {
match tag.kind {
FIFF_BLOCK_START => {
let block_kind = read_i32(reader, &tag).unwrap_or(0);
stack.push(Node {
block: block_kind,
entries: Vec::new(),
children: Vec::new(),
});
}
FIFF_BLOCK_END => {
let finished = stack.pop().unwrap_or_default();
if let Some(parent) = stack.last_mut() {
parent.children.push(finished);
}
}
_ => {
if let Some(node) = stack.last_mut() {
node.entries.push(tag);
}
}
}
}
while stack.len() > 1 {
let orphan = stack.pop().unwrap();
if let Some(parent) = stack.last_mut() {
parent.children.push(orphan);
}
}
*root = stack.pop().unwrap_or_default();
Ok(())
}
pub fn scan_directory<R: Read + Seek>(reader: &mut R) -> Result<Vec<TagHeader>> {
let mut directory = Vec::new();
let mut pos: Option<u64> = Some(0);
while let Some(p) = pos {
let tag = read_tag_header(reader, p)?;
pos = tag.next_pos();
directory.push(tag);
}
Ok(directory)
}
pub fn try_load_directory<R: Read + Seek>(reader: &mut R) -> Result<Option<Vec<TagHeader>>> {
let id_tag = read_tag_header(reader, 0)?;
if id_tag.kind != FIFF_FILE_ID {
return Ok(None);
}
let next = match id_tag.next_pos() {
Some(p) => p,
None => return Ok(None),
};
let dir_ptr_tag = read_tag_header(reader, next)?;
if dir_ptr_tag.kind != FIFF_DIR_POINTER {
return Ok(None);
}
let dirpos = read_i32(reader, &dir_ptr_tag)? as i64;
if dirpos <= 0 {
return Ok(None);
}
let dir_tag = read_tag_header(reader, dirpos as u64)?;
if dir_tag.ftype != super::constants::FIFFT_DIR_ENTRY_STRUCT {
return Ok(None);
}
let entries = super::tag::read_directory(reader, &dir_tag)?;
Ok(Some(entries))
}
#[cfg(test)]
mod tests {
use super::*;
fn make_flat_dir(spec: &[(i32, u32, i32, i32)]) -> Vec<TagHeader> {
spec.iter()
.enumerate()
.map(|(i, &(kind, ftype, size, next))| TagHeader {
kind, ftype, size, next, pos: (i as u64) * 100,
})
.collect()
}
#[test]
fn flat_directory_no_blocks() {
let dir = make_flat_dir(&[
(FIFF_NCHAN, FIFFT_INT, 4, 0),
(FIFF_SFREQ, FIFFT_FLOAT, 4, 0),
]);
let root = build_tree(&dir);
assert_eq!(root.entries.len(), 2);
assert!(root.children.is_empty());
}
#[test]
fn single_block() {
let dir = make_flat_dir(&[
(FIFF_BLOCK_START, FIFFT_INT, 4, 0), (FIFF_NCHAN, FIFFT_INT, 4, 0),
(FIFF_BLOCK_END, FIFFT_INT, 4, 0),
]);
let root = build_tree(&dir);
assert_eq!(root.children.len(), 1);
assert_eq!(root.children[0].entries.len(), 1);
}
#[test]
fn nested_blocks() {
let dir = make_flat_dir(&[
(FIFF_BLOCK_START, FIFFT_INT, 4, 0), (FIFF_BLOCK_START, FIFFT_INT, 4, 0), (FIFF_NCHAN, FIFFT_INT, 4, 0),
(FIFF_BLOCK_END, FIFFT_INT, 4, 0), (FIFF_SFREQ, FIFFT_FLOAT, 4, 0),
(FIFF_BLOCK_END, FIFFT_INT, 4, 0), ]);
let root = build_tree(&dir);
assert_eq!(root.children.len(), 1); let outer = &root.children[0];
assert_eq!(outer.children.len(), 1); assert_eq!(outer.entries.len(), 1); assert_eq!(outer.children[0].entries.len(), 1); }
#[test]
fn find_block_depth_first() {
let dir = make_flat_dir(&[
(FIFF_BLOCK_START, FIFFT_INT, 4, 0),
(FIFF_BLOCK_START, FIFFT_INT, 4, 0),
(FIFF_NCHAN, FIFFT_INT, 4, 0),
(FIFF_BLOCK_END, FIFFT_INT, 4, 0),
(FIFF_BLOCK_END, FIFFT_INT, 4, 0),
]);
let root = build_tree(&dir);
assert!(root.find_block(0).is_some());
}
}