mod channel_reader;
mod file_writer;
use std::{
fs::File,
io::{Read, Seek, SeekFrom, Write},
path::Path,
};
use crate::meta_data::Segment;
use crate::{ChannelPath, index::Index};
use crate::{PropertyPath, PropertyValue, error::TdmsError};
use crate::{
io::writer::{LittleEndianWriter, TdmsWriter},
paths::path_group_name,
};
pub use file_writer::TdmsFileWriter;
#[derive(Debug)]
pub struct TdmsFile<F: Read + Seek> {
index: Index,
file: F,
}
impl TdmsFile<File> {
pub fn load(path: &Path) -> Result<Self, TdmsError> {
let file = File::options().read(true).write(true).open(path)?;
Self::new(file)
}
pub fn create(path: &Path) -> Result<Self, TdmsError> {
let file = File::options()
.write(true)
.create(true)
.truncate(true)
.read(true)
.open(path)?;
Self::new(file)
}
}
fn build_index(file: &mut (impl Read + Seek)) -> Result<Index, TdmsError> {
let mut index = Index::new();
file.seek(SeekFrom::Start(0))?;
loop {
match Segment::read(file) {
Ok(segment) => {
let next_segment = index.add_segment(segment)?;
if file.seek(SeekFrom::Start(next_segment)).is_err() {
break;
}
}
Err(TdmsError::EndOfFile) => break,
Err(e) => return Err(e),
}
}
Ok(index)
}
impl<F: Read + Seek> TdmsFile<F> {
pub fn new(mut file: F) -> Result<Self, TdmsError> {
let index = build_index(&mut file)?;
Ok(Self { index, file })
}
pub fn read_property(
&self,
object_path: &PropertyPath,
property: &str,
) -> Result<Option<&PropertyValue>, TdmsError> {
self.index.get_object_property(object_path, property)
}
pub fn read_all_properties(
&self,
object_path: &PropertyPath,
) -> Option<impl Iterator<Item = (&String, &PropertyValue)>> {
self.index.get_object_properties(object_path)
}
pub fn list_groups<'a>(&'a self) -> impl Iterator<Item = &'a str> + 'a {
let mut groups = std::collections::BTreeSet::new();
let paths = self.index.all_paths();
for path in paths {
let group_name = path_group_name(path);
if let Some(group_name) = group_name {
groups.insert(group_name);
}
}
groups.into_iter()
}
pub fn list_channels_in_group<'a: 'c, 'b: 'c, 'c>(
&'a self,
group: &'b PropertyPath,
) -> impl Iterator<Item = ChannelPath> + 'c {
let paths = self.index.paths_starting_with(group.path());
paths.filter_map(|path| ChannelPath::try_from(path).ok())
}
}
impl<F: Write + Read + Seek> TdmsFile<F> {
pub fn writer(
&mut self,
) -> Result<TdmsFileWriter<'_, F, LittleEndianWriter<&mut F>>, TdmsError> {
self.file.seek(SeekFrom::End(0))?;
Ok(TdmsFileWriter::new(
&mut self.index,
LittleEndianWriter::from_writer(&mut self.file),
))
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use crate::DataLayout;
use super::*;
fn new_empty_file() -> TdmsFile<Cursor<Vec<u8>>> {
let buffer = Vec::new();
let cursor = Cursor::new(buffer);
TdmsFile::new(cursor).unwrap()
}
#[test]
fn test_can_load_empty_buffer() {
let buffer = Vec::new();
let mut cursor = Cursor::new(buffer);
let result = build_index(&mut cursor);
assert!(result.is_ok());
}
#[test]
fn test_list_groups_with_properties_single() {
let mut file = new_empty_file();
let mut writer = file.writer().unwrap();
writer
.write_properties(
&PropertyPath::group("group"),
&[("name", PropertyValue::String("my_channel".to_string()))],
)
.unwrap();
drop(writer);
let groups: Vec<_> = file.list_groups().collect();
assert_eq!(groups.len(), 1);
assert_eq!(groups[0], "group");
}
#[test]
fn test_list_groups_with_properties_multiple() {
let mut file = new_empty_file();
let mut writer = file.writer().unwrap();
writer
.write_properties(
&PropertyPath::group("group"),
&[("name", PropertyValue::String("my_channel".to_string()))],
)
.unwrap();
writer
.write_properties(
&PropertyPath::group("group2"),
&[("name", PropertyValue::String("my_channel".to_string()))],
)
.unwrap();
drop(writer);
let groups: Vec<_> = file.list_groups().collect();
assert_eq!(groups.len(), 2);
assert_eq!(groups[0], "group");
assert_eq!(groups[1], "group2");
}
#[test]
fn test_list_implicit_groups_from_channels() {
let mut file = new_empty_file();
let mut writer = file.writer().unwrap();
writer
.write_channels(
&[ChannelPath::new("group", "channel")],
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
DataLayout::Interleaved,
)
.unwrap();
drop(writer);
let groups: Vec<_> = file.list_groups().collect();
assert_eq!(groups.len(), 1);
assert_eq!(groups[0], "group");
}
#[test]
fn test_list_channels_in_group_single() {
let mut file = new_empty_file();
let mut writer = file.writer().unwrap();
writer
.write_channels(
&[ChannelPath::new("group", "channel")],
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
DataLayout::Interleaved,
)
.unwrap();
drop(writer);
let channels: Vec<_> = file
.list_channels_in_group(&PropertyPath::group("group"))
.collect();
assert_eq!(channels.len(), 1);
assert_eq!(channels[0], ChannelPath::new("group", "channel"));
}
#[test]
fn test_list_channels_in_group_multiple() {
let mut file = new_empty_file();
let mut writer = file.writer().unwrap();
writer
.write_channels(
&[ChannelPath::new("group", "channel")],
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
DataLayout::Interleaved,
)
.unwrap();
writer
.write_channels(
&[ChannelPath::new("group", "channel2")],
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
DataLayout::Interleaved,
)
.unwrap();
drop(writer);
let channels: Vec<_> = file
.list_channels_in_group(&PropertyPath::group("group"))
.collect();
assert_eq!(channels.len(), 2);
assert_eq!(channels[0], ChannelPath::new("group", "channel"));
assert_eq!(channels[1], ChannelPath::new("group", "channel2"));
}
#[test]
fn test_list_channels_in_group_none() {
let mut file = new_empty_file();
let mut writer = file.writer().unwrap();
writer
.write_channels(
&[ChannelPath::new("group", "channel")],
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
DataLayout::Interleaved,
)
.unwrap();
drop(writer);
let channels: Vec<_> = file
.list_channels_in_group(&PropertyPath::group("group2"))
.collect();
assert_eq!(channels.len(), 0);
}
}