use std::{
fs,
io::{self, Read as _, Write as _},
path::{Path, PathBuf},
};
use crate::{BUFFER_CAPACITY_DEFAULT, CHUNK_SIZE_DEFAULT};
#[cfg(feature = "async_std")]
pub mod async_std {
pub use crate::async_std::split::SplitAsyncExt;
}
#[cfg(feature = "smol")]
pub mod smol {
pub use crate::smol::split::SplitAsyncExt;
}
#[cfg(feature = "tokio")]
pub mod tokio {
pub use crate::tokio::split::SplitAsyncExt;
}
#[derive(Debug, Clone)]
pub struct SplitResult {
pub file_size: usize,
pub total_chunks: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SplitError {
InFileNotFound,
InFileNotFile,
InFileNotSet,
InFileNotOpened,
InFileNotRead,
OutDirNotCreated,
OutDirNotDir,
OutDirNotSet,
OutFileNotOpened,
OutFileNotWritten,
}
impl SplitError {
pub fn as_code(&self) -> &str {
match self {
| Self::InFileNotFound => "in_file_not_found",
| Self::InFileNotFile => "in_file_not_file",
| Self::InFileNotSet => "in_file_not_set",
| Self::InFileNotOpened => "in_file_not_opened",
| Self::InFileNotRead => "in_file_not_read",
| Self::OutDirNotCreated => "out_dir_not_created",
| Self::OutDirNotDir => "out_dir_not_dir",
| Self::OutDirNotSet => "out_dir_not_set",
| Self::OutFileNotOpened => "out_file_not_opened",
| Self::OutFileNotWritten => "out_file_not_written",
}
}
pub fn to_code(&self) -> String {
self.as_code().to_string()
}
pub fn as_message(&self) -> &str {
match self {
| Self::InFileNotFound => "The input file not found.",
| Self::InFileNotFile => "The input file is not a file.",
| Self::InFileNotSet => "The input file is not set.",
| Self::InFileNotOpened => "The input file could not be opened.",
| Self::InFileNotRead => "The input file could not be read.",
| Self::OutDirNotCreated => {
"The output directory could not be created."
},
| Self::OutDirNotDir => "The output directory is not a directory.",
| Self::OutDirNotSet => "The output directory is not set.",
| Self::OutFileNotOpened => {
"The output file could not be created or opened."
},
| Self::OutFileNotWritten => {
"The output file could not be written."
},
}
}
pub fn to_message(&self) -> String {
self.as_message().to_string()
}
}
#[derive(Debug, Clone)]
pub struct Split {
pub in_file: Option<PathBuf>,
pub out_dir: Option<PathBuf>,
pub chunk_size: usize,
pub buffer_capacity: usize,
}
impl Split {
pub fn new() -> Self {
Self {
in_file: None,
out_dir: None,
chunk_size: CHUNK_SIZE_DEFAULT,
buffer_capacity: BUFFER_CAPACITY_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 buffer_capacity(
mut self,
capacity: usize,
) -> Self {
self.buffer_capacity = capacity;
self
}
pub fn run(&self) -> Result<SplitResult, SplitError> {
let in_file: &Path = match self.in_file {
| Some(ref p) => {
let p: &Path = p.as_path();
if !p.exists() {
return Err(SplitError::InFileNotFound);
}
if !p.is_file() {
return Err(SplitError::InFileNotFile);
}
p
},
| None => return Err(SplitError::InFileNotSet),
};
let out_dir: &Path = match self.out_dir {
| Some(ref p) => {
let p: &Path = p.as_path();
if !p.exists() {
fs::create_dir_all(p)
.map_err(|_| SplitError::OutDirNotCreated)?
} else if p.is_file() {
return Err(SplitError::OutDirNotDir);
}
p
},
| None => return Err(SplitError::OutDirNotSet),
};
let chunk_size: usize = self.chunk_size;
let buffer_capacity: usize = self.buffer_capacity;
let input_file: fs::File = fs::OpenOptions::new()
.read(true)
.open(in_file)
.map_err(|_| SplitError::InFileNotOpened)?;
let file_size: usize =
input_file.metadata().map_err(|_| SplitError::InFileNotRead)?.len()
as usize;
let mut reader: io::BufReader<fs::File> =
io::BufReader::with_capacity(buffer_capacity, input_file);
let mut buffer: Vec<u8> = vec![0; chunk_size];
let mut total_chunks: usize = 0;
loop {
let mut offset: usize = 0;
while offset < chunk_size {
match reader.read(&mut buffer[offset..]) {
| Ok(0) => break,
| Ok(n) => offset += n,
| Err(_) => return Err(SplitError::InFileNotRead),
};
}
if offset == 0 {
break;
}
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)
.map_err(|_| SplitError::OutFileNotOpened)?;
let mut writer: io::BufWriter<fs::File> =
io::BufWriter::with_capacity(buffer_capacity, output);
writer
.write_all(&buffer[..offset])
.map_err(|_| SplitError::OutFileNotWritten)?;
writer.flush().map_err(|_| SplitError::OutFileNotWritten)?;
total_chunks += 1;
}
Ok(SplitResult { file_size, total_chunks })
}
}
impl Default for Split {
fn default() -> Self {
Self::new()
}
}