use std::io::{Read, Write};
use super::camera::StreamOpen;
#[derive(Debug, PartialEq, Eq)]
pub enum CopyEnd {
Eof,
Cancelled,
CapReached,
Errored,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct RecordStats {
pub bytes: u64,
pub connections: u32,
pub failures: u32,
}
pub fn copy_until(
reader: &mut dyn Read,
sink: &mut dyn Write,
cancel: &dyn Fn() -> bool,
cap_remaining: u64,
buf: &mut [u8],
) -> (CopyEnd, u64) {
let mut written: u64 = 0;
loop {
if cancel() {
return (CopyEnd::Cancelled, written);
}
if written >= cap_remaining {
return (CopyEnd::CapReached, written);
}
let n = match reader.read(buf) {
Ok(0) => return (CopyEnd::Eof, written),
Ok(n) => n,
Err(_) => return (CopyEnd::Errored, written),
};
let take = (n as u64).min(cap_remaining - written) as usize;
if sink.write_all(&buf[..take]).is_err() {
return (CopyEnd::Errored, written);
}
written += take as u64;
if take < n {
return (CopyEnd::CapReached, written);
}
}
}
pub fn record_loop(
open: &StreamOpen,
sink: &mut dyn Write,
cancel: &dyn Fn() -> bool,
max_bytes: u64,
buf_size: usize,
backoff: &dyn Fn(u32),
) -> RecordStats {
let mut stats = RecordStats::default();
let mut buf = vec![0u8; buf_size.max(1)];
let mut attempts: u32 = 0;
loop {
if cancel() || stats.bytes >= max_bytes {
return stats;
}
if attempts > 0 {
backoff(attempts);
if cancel() {
return stats; }
}
attempts += 1;
match (open)() {
Ok(mut opened) => {
stats.connections += 1;
let (end, n) = copy_until(
&mut *opened.reader,
sink,
cancel,
max_bytes - stats.bytes,
&mut buf,
);
stats.bytes += n; match end {
CopyEnd::Eof => {} CopyEnd::Errored => stats.failures += 1, CopyEnd::Cancelled | CopyEnd::CapReached => return stats,
}
}
Err(_) => stats.failures += 1, }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::server::camera::OpenedCameraStream;
use std::io::Cursor;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
fn never() -> bool {
false
}
#[test]
fn copy_until_writes_everything_then_reports_eof() {
let mut r = Cursor::new(b"hello world".to_vec());
let mut sink: Vec<u8> = Vec::new();
let mut buf = [0u8; 4];
let (end, n) = copy_until(&mut r, &mut sink, &never, 1_000, &mut buf);
assert_eq!(end, CopyEnd::Eof);
assert_eq!(n, 11);
assert_eq!(sink, b"hello world");
}
#[test]
fn copy_until_stops_on_cancel_without_reading() {
let mut r = Cursor::new(b"data".to_vec());
let mut sink: Vec<u8> = Vec::new();
let mut buf = [0u8; 8];
let (end, n) = copy_until(&mut r, &mut sink, &|| true, 1_000, &mut buf);
assert_eq!(end, CopyEnd::Cancelled);
assert_eq!(n, 0);
assert!(sink.is_empty());
}
struct BytesThenErr {
data: Vec<u8>,
pos: usize,
}
impl Read for BytesThenErr {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.pos >= self.data.len() {
return Err(std::io::Error::new(
std::io::ErrorKind::ConnectionReset,
"drop",
));
}
let n = buf.len().min(self.data.len() - self.pos);
buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]);
self.pos += n;
Ok(n)
}
}
#[test]
fn copy_until_keeps_bytes_written_before_a_read_error() {
let mut r = BytesThenErr {
data: b"abc".to_vec(),
pos: 0,
};
let mut sink: Vec<u8> = Vec::new();
let mut buf = [0u8; 2];
let (end, n) = copy_until(&mut r, &mut sink, &never, 1_000, &mut buf);
assert_eq!(end, CopyEnd::Errored);
assert_eq!(n, 3);
assert_eq!(sink, b"abc");
}
#[test]
fn copy_until_never_writes_past_the_cap() {
let mut r = Cursor::new(b"0123456789".to_vec());
let mut sink: Vec<u8> = Vec::new();
let mut buf = [0u8; 8];
let (end, n) = copy_until(&mut r, &mut sink, &never, 4, &mut buf);
assert_eq!(end, CopyEnd::CapReached);
assert_eq!(n, 4);
assert_eq!(sink, b"0123");
}
fn seq_opener(entries: Vec<Option<Vec<u8>>>) -> (StreamOpen, Arc<AtomicUsize>) {
let idx = Arc::new(AtomicUsize::new(0));
let counter = idx.clone();
let open: StreamOpen = Arc::new(move || {
let i = idx.fetch_add(1, Ordering::SeqCst);
match entries.get(i) {
Some(Some(data)) => Ok(OpenedCameraStream {
content_type: "multipart/x-mixed-replace".to_string(),
reader: Box::new(Cursor::new(data.clone())),
}),
_ => Err("no stream".to_string()),
}
});
(open, counter)
}
#[test]
fn record_loop_reconnects_after_each_eof() {
let (open, _) = seq_opener(vec![Some(b"aaa".to_vec()), Some(b"bbb".to_vec())]);
let mut sink: Vec<u8> = Vec::new();
let stats = record_loop(&open, &mut sink, &never, 6, 8, &|_| {});
assert_eq!(stats.connections, 2);
assert_eq!(stats.failures, 0);
assert_eq!(sink, b"aaabbb");
}
#[test]
fn record_loop_counts_a_failed_open_and_retries() {
let (open, _) = seq_opener(vec![None, Some(b"ok".to_vec())]);
let mut sink: Vec<u8> = Vec::new();
let stats = record_loop(&open, &mut sink, &never, 2, 8, &|_| {});
assert_eq!(stats.failures, 1);
assert_eq!(stats.connections, 1);
assert_eq!(sink, b"ok");
}
#[test]
fn record_loop_stops_immediately_when_cancelled_up_front() {
let (open, _idx) = seq_opener(vec![Some(b"x".to_vec())]);
let mut sink: Vec<u8> = Vec::new();
let stats = record_loop(&open, &mut sink, &|| true, 1_000, 8, &|_| {});
assert_eq!(stats.connections, 0);
assert_eq!(stats.bytes, 0);
assert!(sink.is_empty());
}
#[test]
fn record_loop_honours_the_byte_cap() {
let (open, _idx) = seq_opener(vec![Some(vec![7u8; 100])]);
let mut sink: Vec<u8> = Vec::new();
let stats = record_loop(&open, &mut sink, &never, 10, 8, &|_| {});
assert_eq!(stats.bytes, 10);
assert_eq!(stats.connections, 1);
assert_eq!(sink.len(), 10);
}
}