use displaydoc::Display;
use parking_lot::Mutex;
use thiserror::Error;
use tokio::{
fs,
io::{self, AsyncSeekExt},
task,
};
use zip::{result::ZipError, ZipWriter};
use std::{ops::DerefMut, path::Path, sync::Arc};
#[derive(Debug, Display, Error)]
pub enum DestinationError {
Io(#[from] io::Error),
Zip(#[from] ZipError),
Join(#[from] task::JoinError),
}
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub enum DestinationBehavior {
#[default]
AlwaysTruncate,
AppendOrFail,
OptimisticallyAppend,
AppendToNonZip,
}
impl DestinationBehavior {
pub async fn initialize(self, path: &Path) -> Result<ZipWriter<std::fs::File>, DestinationError> {
let (file, with_append) = match self {
Self::AlwaysTruncate => {
let f = fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)
.await?;
(f, false)
},
Self::AppendOrFail => {
let f = fs::OpenOptions::new()
.write(true)
.read(true)
.open(path)
.await?;
(f, true)
},
Self::OptimisticallyAppend => {
match fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.await
{
Ok(f) => (f, false),
Err(e) => match e.kind() {
io::ErrorKind::AlreadyExists => {
let f = fs::OpenOptions::new()
.write(true)
.read(true)
.open(path)
.await?;
(f, true)
},
_ => {
return Err(e.into());
},
},
}
},
Self::AppendToNonZip => {
let mut f = fs::OpenOptions::new()
.write(true)
.read(true)
.open(path)
.await?;
f.seek(io::SeekFrom::End(0)).await?;
(f, false)
},
};
let file = file.into_std().await;
let writer = task::spawn_blocking(move || {
if with_append {
Ok::<_, DestinationError>(ZipWriter::new_append(file)?)
} else {
Ok(ZipWriter::new(file))
}
})
.await??;
Ok(writer)
}
}
pub struct OutputWrapper<O> {
handle: Arc<Mutex<O>>,
}
impl<O> Clone for OutputWrapper<O> {
fn clone(&self) -> Self {
Self {
handle: Arc::clone(&self.handle),
}
}
}
impl<O> OutputWrapper<O> {
pub fn wrap(writer: O) -> Self {
Self {
handle: Arc::new(Mutex::new(writer)),
}
}
pub fn reclaim(self) -> O {
Arc::into_inner(self.handle)
.expect("expected this to be the last strong ref")
.into_inner()
}
pub fn lease(&self) -> impl DerefMut<Target=O>+'_ { self.handle.lock() }
}