use std::{
fs,
path::{Path, PathBuf},
};
#[cfg(feature = "async_std")]
pub mod async_std {
pub use crate::async_std::check::CheckAsyncExt;
}
#[cfg(feature = "smol")]
pub mod smol {
pub use crate::smol::check::CheckAsyncExt;
}
#[cfg(feature = "tokio")]
pub mod tokio {
pub use crate::tokio::check::CheckAsyncExt;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MissingChunks {
pub missing: Vec<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SizeMismatch {
pub expected: usize,
pub actual: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CheckError {
InDirNotFound,
InDirNotDir,
InDirNotSet,
InFileNotOpened,
InFileNotRead,
FileSizeNotSet,
TotalChunksNotSet,
MissingChunks(MissingChunks),
SizeMismatch(SizeMismatch),
}
impl CheckError {
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::InFileNotOpened => "in_file_not_opened",
| Self::InFileNotRead => "in_file_not_read",
| Self::FileSizeNotSet => "file_size_not_set",
| Self::TotalChunksNotSet => "total_chunks_not_set",
| Self::MissingChunks(_) => "missing_chunks",
| Self::SizeMismatch(_) => "size_mismatch",
}
}
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::InFileNotOpened => "The input file could not be opened.",
| Self::InFileNotRead => "The input file could not be read.",
| Self::FileSizeNotSet => "The `file_size` is not set.",
| Self::TotalChunksNotSet => "The `total_chunks` is not set.",
| Self::MissingChunks(_) => {
"Some of the chunks are missing to merge the file."
},
| Self::SizeMismatch(_) => {
"The actual file size is not equal the input file size."
},
}
}
pub fn to_message(&self) -> String {
self.as_message().to_string()
}
}
#[derive(Debug, Clone)]
pub struct Check {
pub in_dir: Option<PathBuf>,
pub file_size: Option<usize>,
pub total_chunks: Option<usize>,
}
impl Check {
pub fn new() -> Self {
Self { in_dir: None, file_size: None, total_chunks: None }
}
pub fn from<P: Into<Check>>(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 file_size(
mut self,
size: usize,
) -> Self {
self.file_size = Some(size);
self
}
pub fn total_chunks(
mut self,
chunks: usize,
) -> Self {
self.total_chunks = Some(chunks);
self
}
pub fn run(&self) -> Result<(), CheckError> {
let in_dir: &Path = match self.in_dir {
| Some(ref p) => {
let p: &Path = p.as_ref();
if !p.exists() {
return Err(CheckError::InDirNotFound);
}
if !p.is_dir() {
return Err(CheckError::InDirNotDir);
}
p
},
| None => return Err(CheckError::InDirNotSet),
};
let file_size: usize =
self.file_size.ok_or(CheckError::FileSizeNotSet)?;
let total_chunks: usize =
self.total_chunks.ok_or(CheckError::TotalChunksNotSet)?;
let mut actual_size: usize = 0;
let mut missing: Vec<usize> = Vec::with_capacity(total_chunks);
for i in 0..total_chunks {
let target_file: PathBuf = in_dir.join(i.to_string());
let file: fs::File =
match fs::OpenOptions::new().read(true).open(&target_file) {
| Ok(f) => f,
| Err(_) => {
missing.push(i);
continue;
},
};
let metadata: fs::Metadata =
file.metadata().map_err(|_| CheckError::InFileNotRead)?;
if !metadata.is_file() {
missing.push(i);
continue;
}
actual_size += metadata.len() as usize;
}
if !missing.is_empty() {
return Err(CheckError::MissingChunks(MissingChunks { missing }));
}
if file_size != actual_size {
return Err(CheckError::SizeMismatch(SizeMismatch {
expected: file_size,
actual: actual_size,
}));
}
Ok(())
}
}
impl Default for Check {
fn default() -> Self {
Self::new()
}
}