use std::borrow::Cow;
use std::future::poll_fn;
use std::io::Result;
use std::num::NonZeroUsize;
use std::pin::Pin;
use pin_project_lite::pin_project;
use tokio::io::{AsyncRead, AsyncWrite};
mod shared;
pub use shared::block::{BLOCK_SIZE, Header};
mod read;
pub use read::ReadError;
mod write;
pub use write::WriteError;
#[cfg(feature = "streams")]
use read::Entries;
use read::NextEntry;
use shared::buffer::Buf;
use shared::state::State;
const DEFAULT_BUFFER_CAPACITY: usize = 8;
pin_project! {
#[derive(Debug)]
pub struct Archive<T> {
buf: Buf,
state: State,
#[pin]
io: T,
}
}
impl<T> Archive<T> {
pub fn new(io: T) -> Self {
Self::with_capacity(io, NonZeroUsize::new(DEFAULT_BUFFER_CAPACITY).unwrap())
}
pub fn with_capacity(io: T, capacity: NonZeroUsize) -> Self {
let cap = capacity
.get()
.checked_mul(BLOCK_SIZE)
.expect("capacity overflow");
Self {
buf: Buf::new(cap),
state: State::default(),
io,
}
}
pub fn into_inner(self) -> T {
self.io
}
}
impl<R: AsyncRead + Unpin> Archive<R> {
#[inline]
pub fn next_entry(&mut self) -> NextEntry<'_, R> {
NextEntry::new(self)
}
#[cfg(feature = "streams")]
#[inline]
pub fn entries(&mut self) -> Entries<'_, R> {
Entries::new(self)
}
}
impl<W: AsyncWrite + Unpin> Archive<W> {
#[inline]
pub async fn add_entry(&mut self, header: Header) -> Result<Entry<'_, W>> {
let mut pin = Pin::new(self);
poll_fn(|cx| pin.as_mut().poll_write_header(cx, &header)).await?;
Entry::new(pin, header)
}
#[inline]
pub async fn finish(&mut self) -> Result<()> {
let mut pin = Pin::new(self);
poll_fn(|cx| pin.as_mut().poll_finish(cx)).await
}
}
pin_project! {
#[derive(Debug)]
pub struct Entry<'a, T> {
archive: Pin<&'a mut Archive<T>>,
header: Header,
}
}
impl<'a, T> Entry<'a, T> {
fn new(archive: Pin<&'a mut Archive<T>>, header: Header) -> Result<Self> {
let cksum = header.cksum()?;
assert!(cksum > 0, "header must be finalized before creating entry");
let _ = header.size()?;
Ok(Self { archive, header })
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn size(&self) -> u64 {
self.header.size().unwrap()
}
pub fn len(&self) -> u64 {
self.header.entry_size().unwrap()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn path(&self) -> Cow<[u8]> {
self.header.path_bytes()
}
pub fn path_lossy(&self) -> String {
String::from_utf8_lossy(&self.header.path_bytes()).to_string()
}
}
impl<R: AsyncRead + Unpin> Entry<'_, R> {
#[inline]
pub async fn skip(&mut self) -> Result<()> {
let mut pin = Pin::new(self);
poll_fn(|cx| pin.as_mut().poll_skip(cx)).await
}
}
impl<W: AsyncWrite + Unpin> Entry<'_, W> {
#[inline]
pub async fn finish(&mut self) -> Result<()> {
let mut pin = Pin::new(self);
poll_fn(|cx| pin.as_mut().poll_shutdown(cx)).await
}
}
#[doc(no_inline)]
pub use tar as sync;
#[cfg(feature = "tracing")]
const TRACING_ENABLED: bool = true;
#[cfg(not(feature = "tracing"))]
const TRACING_ENABLED: bool = false;
#[cfg(test)]
#[test]
fn assert_autotraits() {
fn is_unpin<T: Unpin>() {}
is_unpin::<Archive<()>>();
is_unpin::<Entry<()>>();
fn is_send<T: Send>() {}
is_send::<Archive<()>>();
is_send::<Entry<()>>();
is_send::<ReadError>();
is_send::<WriteError>();
fn is_sync<T: Sync>() {}
is_sync::<Archive<()>>();
is_sync::<Entry<()>>();
is_sync::<ReadError>();
is_sync::<WriteError>();
}