use std::{
io::{self, Write},
path::{Path, PathBuf},
};
use snafu::{IntoError, ResultExt};
use tokio::fs;
use crate::storage::{
BackendError, OtherIoSnafu, StorageLocation, StorageResult, TempFileGuard, create_parent_dir,
join_local,
};
struct LocalSink {
tmp_path: PathBuf,
final_path: PathBuf,
writer: io::BufWriter<std::fs::File>,
guard: TempFileGuard,
}
impl LocalSink {
async fn open(location: &StorageLocation, rel_path: &Path) -> StorageResult<Self> {
let final_path = join_local(location, rel_path);
create_parent_dir(&final_path).await?;
let tmp_path = final_path.with_extension("tmp");
let file = std::fs::File::create(&tmp_path)
.map_err(BackendError::Local)
.context(OtherIoSnafu {
path: tmp_path.display().to_string(),
})?;
let writer = io::BufWriter::new(file);
let guard = TempFileGuard::new(tmp_path.clone());
Ok(Self {
tmp_path,
final_path,
writer,
guard,
})
}
fn writer(&mut self) -> &mut dyn Write {
&mut self.writer
}
async fn finish(&mut self) -> StorageResult<()> {
self.writer
.flush()
.map_err(BackendError::Local)
.context(OtherIoSnafu {
path: self.tmp_path.display().to_string(),
})?;
self.writer
.get_ref()
.sync_all()
.map_err(BackendError::Local)
.context(OtherIoSnafu {
path: self.tmp_path.display().to_string(),
})?;
fs::rename(&self.tmp_path, &self.final_path)
.await
.map_err(BackendError::Local)
.context(OtherIoSnafu {
path: self.final_path.display().to_string(),
})?;
self.guard.disarm();
Ok(())
}
}
enum OutputSinkInner {
Local(LocalSink),
}
pub struct OutputSink {
inner: OutputSinkInner,
}
impl OutputSink {
pub fn writer(&mut self) -> &mut dyn Write {
match &mut self.inner {
OutputSinkInner::Local(s) => s.writer(),
}
}
pub async fn finish(self) -> StorageResult<()> {
match self.inner {
OutputSinkInner::Local(mut s) => s.finish().await,
}
}
}
pub async fn open_output_sink(
location: &StorageLocation,
rel_path: &Path,
) -> StorageResult<OutputSink> {
match location {
StorageLocation::Local(_) => {
let sink = LocalSink::open(location, rel_path).await?;
Ok(OutputSink {
inner: OutputSinkInner::Local(sink),
})
}
}
}
#[derive(Debug, Clone)]
pub struct OutputLocation {
pub storage: StorageLocation,
pub rel_path: PathBuf,
}
impl OutputLocation {
pub fn parse(spec: &str) -> StorageResult<OutputLocation> {
let trimmed = spec.trim();
if trimmed.is_empty() {
return Err(OtherIoSnafu {
path: "<empty output location>".to_string(),
}
.into_error(BackendError::Local(std::io::Error::new(
io::ErrorKind::InvalidInput,
"output location is empty",
))));
}
let storage = StorageLocation::parse(trimmed)?;
match &storage {
StorageLocation::Local(_) => {
let path = PathBuf::from(trimmed);
let base = PathBuf::from(".");
let rel_path = path;
Ok(OutputLocation {
storage: StorageLocation::Local(base),
rel_path,
})
}
}
}
}