use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use serde::{Deserialize, Serialize};
use super::frame::{FrameRecord, RhythmMeta};
const RLOG_MAGIC: &[u8; 4] = b"RLOG";
const RLOG_VERSION: u16 = 2;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RlogHeader {
magic: [u8; 4],
version: u16,
rhythm_count: u16,
rhythms: Vec<RhythmMeta>,
}
pub struct FrameWriter {
writer: Mutex<BufWriter<File>>,
start: Instant,
}
impl FrameWriter {
pub fn create(path: impl AsRef<Path>, rhythms: &[RhythmMeta]) -> io::Result<Arc<Self>> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
let header = RlogHeader {
magic: *RLOG_MAGIC,
version: RLOG_VERSION,
rhythm_count: rhythms.len() as u16,
rhythms: rhythms.to_vec(),
};
let header_json = serde_json::to_string(&header)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
writeln!(writer, "{}", header_json)?;
writer.flush()?;
Ok(Arc::new(Self {
writer: Mutex::new(writer),
start: Instant::now(),
}))
}
pub fn elapsed_ns(&self) -> u64 {
self.start.elapsed().as_nanos() as u64
}
pub fn write_frame(&self, frame: &FrameRecord) {
let json = serde_json::to_string(frame).expect("FrameRecord serialize failed");
let mut w = self.writer.lock().expect("FrameWriter lock poisoned");
let _ = writeln!(w, "{}", json);
}
pub fn flush(&self) {
let mut w = self.writer.lock().expect("FrameWriter lock poisoned");
let _ = w.flush();
}
}
impl Drop for FrameWriter {
fn drop(&mut self) {
self.flush();
}
}
pub struct RlogWriter {
writer: Arc<FrameWriter>,
}
impl RlogWriter {
pub fn create(path: impl AsRef<Path>, rhythms: &[RhythmMeta]) -> io::Result<Self> {
let writer = FrameWriter::create(path, rhythms)?;
Ok(Self { writer })
}
pub fn writer(&self) -> Arc<FrameWriter> {
self.writer.clone()
}
pub fn flush(&self) {
self.writer.flush();
}
}
pub struct RlogReader {
pub header: RlogHeader,
pub frames: Vec<FrameRecord>,
}
impl RlogReader {
pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut lines = reader.lines();
let header_line = lines
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "empty rlog file"))??;
let header: RlogHeader = serde_json::from_str(&header_line)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
if header.magic != *RLOG_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid rlog magic bytes",
));
}
if header.version != RLOG_VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"unsupported rlog version: expected {}, got {}",
RLOG_VERSION, header.version
),
));
}
let mut frames = Vec::new();
for line in lines {
let line = line?;
if line.is_empty() {
continue;
}
let frame: FrameRecord = serde_json::from_str(&line)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
frames.push(frame);
}
Ok(Self { header, frames })
}
pub fn frames_for(&self, rhythm_id: u16) -> Vec<FrameRecord> {
self.frames
.iter()
.filter(|f| f.rhythm_id == rhythm_id)
.cloned()
.collect()
}
pub fn rhythm_meta(&self, rhythm_id: u16) -> Option<&RhythmMeta> {
self.header
.rhythms
.iter()
.find(|m| m.rhythm_id == rhythm_id)
}
pub fn frames_sorted(&self) -> Vec<&FrameRecord> {
let mut refs: Vec<&FrameRecord> = self.frames.iter().collect();
refs.sort_by_key(|f| f.global_seq);
refs
}
pub fn frame_count(&self) -> usize {
self.frames.len()
}
}