use std::{
fs,
io::{self, Read as _, Write as _},
path::{Path, PathBuf},
};
use crate::BUFFER_CAPACITY_DEFAULT;
#[cfg(feature = "async_std")]
pub mod async_std {
pub use crate::async_std::merge::MergeAsyncExt;
}
#[cfg(feature = "smol")]
pub mod smol {
pub use crate::smol::merge::MergeAsyncExt;
}
#[cfg(feature = "tokio")]
pub mod tokio {
pub use crate::tokio::merge::MergeAsyncExt;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MergeError {
InDirNotFound,
InDirNotDir,
InDirNotSet,
InDirNotRead,
InDirNoFile,
InFileNotOpened,
InFileNotRead,
OutDirNotCreated,
OutFileNotSet,
OutFileNotRemoved,
OutFileNotOpened,
OutFileNotWritten,
}
impl MergeError {
pub fn as_code(&self) -> &str {
match self {
| Self::InDirNotFound => "in_dir_not_found",
| Self::InDirNotDir => "in_dir_not_dir",
| Self::InDirNotSet => "in_dir_not_set",
| Self::InDirNotRead => "in_dir_not_read",
| Self::InDirNoFile => "in_dir_no_file",
| Self::InFileNotOpened => "in_file_not_opened",
| Self::InFileNotRead => "in_file_not_read",
| Self::OutDirNotCreated => "out_dir_not_created",
| Self::OutFileNotSet => "out_file_not_set",
| Self::OutFileNotRemoved => "out_file_not_removed",
| 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::InDirNotFound => "The input directory not found.",
| Self::InDirNotDir => "The input directory is not a directory.",
| Self::InDirNotSet => "The input directory is not set.",
| Self::InDirNotRead => "The input directory could not be read.",
| Self::InDirNoFile => "The input directory has no file.",
| 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::OutFileNotSet => "The output file is not set.",
| Self::OutFileNotRemoved => {
"The output file could not be removed."
},
| Self::OutFileNotOpened => "The output file could not be 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 Merge {
pub in_dir: Option<PathBuf>,
pub out_file: Option<PathBuf>,
pub buffer_capacity: usize,
}
impl Merge {
pub fn new() -> Self {
Self {
in_dir: None,
out_file: None,
buffer_capacity: BUFFER_CAPACITY_DEFAULT,
}
}
pub fn from<P: Into<Merge>>(process: P) -> Self {
process.into()
}
pub fn in_dir<InDir: AsRef<Path>>(
mut self,
path: InDir,
) -> Self {
self.in_dir = Some(path.as_ref().to_path_buf());
self
}
pub fn out_file<OutFile: AsRef<Path>>(
mut self,
path: OutFile,
) -> Self {
self.out_file = Some(path.as_ref().to_path_buf());
self
}
pub fn buffer_capacity(
mut self,
capacity: usize,
) -> Self {
self.buffer_capacity = capacity;
self
}
pub fn run(&self) -> Result<(), MergeError> {
let in_dir: &Path = match self.in_dir {
| Some(ref p) => {
let p: &Path = p.as_ref();
if !p.exists() {
return Err(MergeError::InDirNotFound);
}
if !p.is_dir() {
return Err(MergeError::InDirNotDir);
}
p
},
| None => return Err(MergeError::InDirNotSet),
};
let out_file: &Path = match self.out_file {
| Some(ref p) => {
let p: &Path = p.as_ref();
if p.exists() {
if p.is_dir() {
fs::remove_dir_all(p)
.map_err(|_| MergeError::OutFileNotRemoved)?;
} else {
fs::remove_file(p)
.map_err(|_| MergeError::OutFileNotRemoved)?;
}
}
if let Some(parent) = p.parent() {
fs::create_dir_all(parent)
.map_err(|_| MergeError::OutDirNotCreated)?;
}
p
},
| None => return Err(MergeError::OutFileNotSet),
};
let buffer_capacity: usize = self.buffer_capacity;
let output: fs::File = fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(out_file)
.map_err(|_| MergeError::OutFileNotOpened)?;
let mut writer: io::BufWriter<fs::File> =
io::BufWriter::with_capacity(buffer_capacity, output);
let mut entries: Vec<PathBuf> = {
let read_dir: fs::ReadDir =
fs::read_dir(in_dir).map_err(|_| MergeError::InDirNotRead)?;
read_dir
.filter_map(Result::ok)
.filter(|entry| entry.path().is_file())
.map(|entry| entry.path())
.collect()
};
if entries.is_empty() {
return Err(MergeError::InDirNoFile);
}
entries.sort_by_key(|entry| {
entry
.file_name()
.unwrap()
.to_str()
.unwrap()
.parse::<usize>()
.unwrap()
});
for entry in entries {
let input: fs::File = fs::OpenOptions::new()
.read(true)
.open(&entry)
.map_err(|_| MergeError::InFileNotOpened)?;
let mut reader: io::BufReader<fs::File> =
io::BufReader::with_capacity(buffer_capacity, input);
let mut buffer: Vec<u8> = vec![0; buffer_capacity];
loop {
let read: usize = reader
.read(&mut buffer)
.map_err(|_| MergeError::InFileNotRead)?;
if read == 0 {
break;
}
writer
.write_all(&buffer[..read])
.map_err(|_| MergeError::OutFileNotWritten)?;
}
}
writer.flush().map_err(|_| MergeError::OutFileNotWritten)?;
Ok(())
}
}
impl Default for Merge {
fn default() -> Self {
Self::new()
}
}