effectcraft/crates/export/src/out.rs

78 lines
2.6 KiB
Rust

//! Output files: the file system, or an in-memory [`Sink`] chosen per job (the web app, which
//! offers every written file as a download; tests).
use std::io::{BufWriter, Cursor, Seek, SeekFrom, Write};
use crate::{ExportError, Result, io};
/// Receives each finished output file (path as resolved, complete contents) instead of the file
/// system. Called once per file; image sequences call it from the render threads.
pub type Sink = dyn Fn(&str, Vec<u8>) + Send + Sync;
/// One output file being written.
pub(crate) enum Out<'a> {
File(BufWriter<std::fs::File>),
Mem { path: String, cur: Cursor<Vec<u8>>, sink: &'a Sink },
}
/// Create `path` (parent directories included) or an in-memory file for `sink`.
pub(crate) fn create<'a>(sink: Option<&'a Sink>, path: &str) -> Result<Out<'a>> {
match sink {
Some(sink) => Ok(Out::Mem { path: path.to_string(), cur: Cursor::new(Vec::new()), sink }),
None => {
if let Some(dir) = std::path::Path::new(path).parent().filter(|d| !d.as_os_str().is_empty()) {
std::fs::create_dir_all(dir).map_err(io)?;
}
Ok(Out::File(BufWriter::new(std::fs::File::create(path).map_err(io)?)))
}
}
}
/// Whether `path` already exists (never for a sink: it has no memory of earlier jobs).
pub(crate) fn exists(sink: Option<&Sink>, path: &str) -> bool {
sink.is_none() && std::path::Path::new(path).exists()
}
impl Out<'_> {
/// Flush and close; a sink receives the contents now. Returns the file size.
pub(crate) fn finish(self) -> Result<u64> {
match self {
Out::File(mut w) => {
w.flush().map_err(io)?;
let f = w.into_inner().map_err(|e| ExportError::Io(e.to_string()))?;
Ok(f.metadata().map(|m| m.len()).unwrap_or(0))
}
Out::Mem { path, cur, sink } => {
let data = cur.into_inner();
let n = data.len() as u64;
sink(&path, data);
Ok(n)
}
}
}
}
impl Write for Out<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
Out::File(w) => w.write(buf),
Out::Mem { cur, .. } => cur.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
match self {
Out::File(w) => w.flush(),
Out::Mem { .. } => Ok(()),
}
}
}
impl Seek for Out<'_> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
match self {
Out::File(w) => w.seek(pos),
Out::Mem { cur, .. } => cur.seek(pos),
}
}
}