mod file;
mod player;
mod tcp;
pub use player::{Player, PlayerClosedError};
use std::io::{self, Write};
use anyhow::{Result, ensure};
use log::{debug, info};
use file::File;
use tcp::Tcp;
use crate::config::Config;
pub trait Output: Write + Send {
fn set_header(&mut self, header: &[u8]) -> io::Result<()>;
fn should_wait(&self) -> bool {
false
}
fn wait_for_output(&mut self) -> io::Result<()> {
Ok(())
}
}
#[derive(Default)]
pub struct Writer {
outputs: Vec<Box<dyn Output>>,
}
impl Output for Writer {
fn set_header(&mut self, header: &[u8]) -> io::Result<()> {
debug!("Outputting segment header");
self.handle_outputs(|output| output.set_header(header))
}
fn should_wait(&self) -> bool {
if self.outputs.len() == 1
&& let Some(output) = self.outputs.first()
{
return output.should_wait();
}
false
}
fn wait_for_output(&mut self) -> io::Result<()> {
info!("Waiting for outputs...");
for output in &mut self.outputs {
output.wait_for_output()?;
}
Ok(())
}
}
impl Write for Writer {
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
unreachable!();
}
fn flush(&mut self) -> io::Result<()> {
self.handle_outputs(Write::flush)?;
debug!("Finished writing segment");
Ok(())
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.handle_outputs(|output| output.write_all(buf))
}
}
impl Writer {
pub fn new() -> Result<Self> {
ensure!(Config::get().has_output(), "No output configured");
let mut writer = Self::default();
writer.add_output(Player::new()?);
writer.add_output(Tcp::new()?);
writer.add_output(File::new()?);
Ok(writer)
}
fn add_output(&mut self, output: Option<impl Output + 'static>) {
if let Some(output) = output {
self.outputs.push(Box::new(output));
}
}
fn handle_outputs<F>(&mut self, mut f: F) -> io::Result<()>
where
F: FnMut(&mut Box<dyn Output>) -> io::Result<()>,
{
let has_multiple = self.outputs.len() > 1;
let mut result = Ok(());
self.outputs.retain_mut(|output| {
if let Err(error) = f(output) {
#[allow(clippy::redundant_closure_for_method_calls)] if !(has_multiple && error.get_ref().is_some_and(|e| e.is::<PlayerClosedError>())) {
result = Err(error);
}
return false;
}
true
});
debug_assert!(!self.outputs.is_empty() || self.outputs.is_empty() && result.is_err());
result
}
}