use std::{
fs,
io::{self as io, Read as _, Write as _},
path::{Path, PathBuf},
};
use crate::{BUFFER_CAPACITY_MAX_DEFAULT, CHUNK_SIZE_DEFAULT};
#[cfg(feature = "async-std")]
pub mod async_std {
pub use crate::async_std::split::SplitAsyncExt;
}
#[cfg(feature = "tokio")]
pub mod tokio {
pub use crate::tokio::split::SplitAsyncExt;
}
#[derive(Debug, Clone)]
pub struct Split {
pub in_file: Option<PathBuf>,
pub out_dir: Option<PathBuf>,
pub chunk_size: usize,
pub cap_max: usize,
}
#[derive(Debug, Clone)]
pub struct SplitResult {
pub file_size: usize,
pub total_chunks: usize,
}
impl Split {
pub fn new() -> Self {
Self {
in_file: None,
out_dir: None,
chunk_size: CHUNK_SIZE_DEFAULT,
cap_max: BUFFER_CAPACITY_MAX_DEFAULT,
}
}
pub fn from<P: Into<Split>>(process: P) -> Self {
process.into()
}
pub fn in_file<InFile: AsRef<Path>>(
mut self,
path: InFile,
) -> Self {
self.in_file = Some(path.as_ref().to_path_buf());
self
}
pub fn out_dir<OutDir: AsRef<Path>>(
mut self,
path: OutDir,
) -> Self {
self.out_dir = Some(path.as_ref().to_path_buf());
self
}
pub fn chunk_size(
mut self,
size: usize,
) -> Self {
self.chunk_size = size;
self
}
pub fn max_buffer_capacity(
mut self,
capacity: usize,
) -> Self {
self.cap_max = capacity;
self
}
pub fn run(&self) -> io::Result<SplitResult> {
let in_file: &Path = match self.in_file {
| Some(ref p) => {
let p: &Path = p.as_ref();
if !p.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"in_file path not found",
));
}
if !p.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"in_file is not a path to file",
));
}
p
},
| None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"in_file is not set",
));
},
};
let out_dir: &Path = match self.out_dir {
| Some(ref p) => {
let p: &Path = p.as_ref();
if !p.exists() {
fs::create_dir_all(p)?;
} else {
if p.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"out_dir is not a directory",
));
}
}
p
},
| None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"out_dir is not set",
));
},
};
let chunk_size: usize = self.chunk_size;
let buffer_capacity: usize = chunk_size.min(self.cap_max);
let input: fs::File =
fs::OpenOptions::new().read(true).open(in_file)?;
let file_size: usize = input.metadata()?.len() as usize;
let mut reader: io::BufReader<fs::File> =
io::BufReader::with_capacity(buffer_capacity, input);
let mut buffer: Vec<u8> = vec![0; chunk_size];
let mut total_chunks: usize = 0;
let mut current: usize = 0;
loop {
let read: usize = reader.read(&mut buffer[current..])?;
if read == 0 {
if current > 0 {
let output_path: PathBuf =
out_dir.join(total_chunks.to_string());
let output: fs::File = fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(output_path)?;
let mut writer: io::BufWriter<fs::File> =
io::BufWriter::with_capacity(buffer_capacity, output);
writer.write_all(&buffer[..current])?;
writer.flush()?;
total_chunks += 1;
}
break;
}
current += read;
if current >= chunk_size {
let output_path: PathBuf =
out_dir.join(total_chunks.to_string());
let output: fs::File = fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(output_path)?;
let mut writer: io::BufWriter<fs::File> =
io::BufWriter::with_capacity(buffer_capacity, output);
writer.write_all(&buffer[..chunk_size])?;
writer.flush()?;
total_chunks += 1;
buffer.copy_within(chunk_size..current, 0);
current -= chunk_size;
}
}
Ok(SplitResult { file_size, total_chunks })
}
}
impl Default for Split {
fn default() -> Self {
Self::new()
}
}