use std::io::{Read, Write};
use std::time::SystemTime;
use crate::error::Result;
use crate::fidelity::FidelityReport;
use crate::format::{CodecCaps, ContainerCaps, FormatId};
use crate::source::Source;
#[derive(Clone, Default)]
pub struct DecodeOpts {
pub threads: Option<usize>,
pub governor: Option<std::sync::Arc<crate::Governor>>,
pub memory_limit: Option<u64>,
}
impl std::fmt::Debug for DecodeOpts {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DecodeOpts")
.field("threads", &self.threads)
.field("governor", &self.governor.as_ref().map(|_| "Governor"))
.field("memory_limit", &self.memory_limit)
.finish()
}
}
#[derive(Clone, Default)]
pub struct EncodeOpts {
pub level: Option<i32>,
pub threads: Option<usize>,
pub governor: Option<std::sync::Arc<crate::Governor>>,
}
impl std::fmt::Debug for EncodeOpts {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EncodeOpts")
.field("level", &self.level)
.field("threads", &self.threads)
.field("governor", &self.governor.as_ref().map(|_| "Governor"))
.finish()
}
}
#[derive(Clone, Default)]
pub struct OpenOpts {
pub password: Option<String>,
pub governor: Option<std::sync::Arc<crate::Governor>>,
}
impl std::fmt::Debug for OpenOpts {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OpenOpts")
.field("password", &self.password)
.field("governor", &self.governor.as_ref().map(|_| "Governor"))
.finish()
}
}
#[derive(Clone, Default)]
pub struct CreateOpts {
pub level: Option<i32>,
pub entry_codec: Option<FormatId>,
pub governor: Option<std::sync::Arc<crate::Governor>>,
}
impl std::fmt::Debug for CreateOpts {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CreateOpts")
.field("level", &self.level)
.field("entry_codec", &self.entry_codec)
.field("governor", &self.governor.as_ref().map(|_| "Governor"))
.finish()
}
}
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub enum EntryKind {
#[default]
File,
Dir,
Symlink {
target: String,
},
Other,
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct EntryMeta {
pub name: String,
pub size: Option<u64>,
pub compressed_size: Option<u64>,
pub mtime: Option<SystemTime>,
pub mode: Option<u32>,
pub uid: Option<u32>,
pub gid: Option<u32>,
pub kind: EntryKind,
pub codec: Option<FormatId>,
}
impl EntryMeta {
pub fn file(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
}
pub struct Entry<'a> {
meta: EntryMeta,
reader: Box<dyn Read + 'a>,
}
impl<'a> Entry<'a> {
pub fn new(meta: EntryMeta, reader: Box<dyn Read + 'a>) -> Self {
Self { meta, reader }
}
pub fn meta(&self) -> &EntryMeta {
&self.meta
}
pub fn reader(&mut self) -> &mut (dyn Read + 'a) {
&mut *self.reader
}
#[cfg(test)]
pub(crate) fn into_reader(self) -> Box<dyn Read + 'a> {
self.reader
}
}
impl std::fmt::Debug for Entry<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Entry")
.field("meta", &self.meta)
.finish_non_exhaustive()
}
}
#[must_use = "a Sink must be completed with `finish`; dropping it skips the \
trailer and the flush, truncating the output"]
pub trait Sink: Write + Send {
fn finish(self: Box<Self>) -> Result<()>;
}
pub struct PlainSink(Box<dyn Write + Send>);
impl PlainSink {
#[allow(clippy::new_ret_no_self)]
pub fn new(w: Box<dyn Write + Send>) -> Box<dyn Sink> {
Box::new(Self(w))
}
}
impl Write for PlainSink {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.0.write(b)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.flush()
}
}
impl Sink for PlainSink {
fn finish(mut self: Box<Self>) -> Result<()> {
self.0.flush()?;
Ok(())
}
}
pub trait Codec: Send + Sync {
fn id(&self) -> FormatId;
fn caps(&self) -> CodecCaps;
fn check_encode_opts(&self, _o: &EncodeOpts) -> Result<()> {
Ok(())
}
fn decoder(&self, src: Box<dyn Source>, o: &DecodeOpts) -> Result<Box<dyn Source>>;
fn encoder(&self, dst: Box<dyn Write + Send>, o: &EncodeOpts) -> Result<Box<dyn Sink>>;
}
pub trait Container: Send + Sync {
fn id(&self) -> FormatId;
fn caps(&self) -> ContainerCaps;
fn open(&self, resolved: crate::ladder::Resolved, o: &OpenOpts)
-> Result<Box<dyn ArchiveRead>>;
fn create(&self, dst: Box<dyn Sink>, o: &CreateOpts) -> Result<Box<dyn ArchiveWrite>>;
}
pub trait ArchiveRead {
fn next_entry(&mut self) -> Result<Option<Entry<'_>>>;
fn by_index(&mut self, index: usize) -> Result<Entry<'_>>;
fn fidelity(&self) -> &FidelityReport;
}
pub trait ArchiveWrite {
fn add(&mut self, meta: &EntryMeta, data: &mut dyn Read) -> Result<()>;
fn finish(self: Box<Self>) -> Result<Box<dyn Sink>>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_plain_sink_flushes_its_destination_on_finish() {
use std::sync::{Arc, Mutex};
#[derive(Default)]
struct Rec {
flushed: bool,
bytes: Vec<u8>,
}
struct W(Arc<Mutex<Rec>>);
impl Write for W {
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().bytes.extend_from_slice(b);
Ok(b.len())
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.lock().unwrap().flushed = true;
Ok(())
}
}
let rec = Arc::new(Mutex::new(Rec::default()));
let mut sink = PlainSink::new(Box::new(W(Arc::clone(&rec))));
sink.write_all(b"payload").unwrap();
assert!(
!rec.lock().unwrap().flushed,
"finish has not been called yet"
);
sink.finish().unwrap();
let r = rec.lock().unwrap();
assert!(
r.flushed,
"PlainSink::finish must flush — a buffered destination loses its tail otherwise"
);
assert_eq!(r.bytes, b"payload");
}
}