use std::{
fs, io,
path::{Path, PathBuf},
};
#[cfg(feature = "async-std")]
pub mod async_std {
pub use crate::async_std::check::CheckAsyncExt;
}
#[cfg(feature = "tokio")]
pub mod tokio {
pub use crate::tokio::check::CheckAsyncExt;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CheckResultErrorType {
Missing,
Size,
}
impl CheckResultErrorType {
pub fn from_code<C: AsRef<str>>(code: C) -> Option<Self> {
match code.as_ref() {
| "missing" => Some(Self::Missing),
| "size" => Some(Self::Size),
| _ => None,
}
}
pub fn as_code(&self) -> &str {
match self {
| Self::Missing => "missing",
| Self::Size => "size",
}
}
pub fn to_code(&self) -> String {
self.as_code().to_string()
}
}
#[derive(Debug, Clone)]
pub struct CheckResultError {
pub error_type: CheckResultErrorType,
pub message: String,
pub missing: Option<Vec<usize>>,
}
#[derive(Debug, Clone)]
pub struct CheckResult {
pub success: bool,
pub error: Option<CheckResultError>,
}
#[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) -> io::Result<CheckResult> {
let in_dir: &Path = match self.in_dir {
| Some(ref p) => {
let p: &Path = p.as_ref();
if !p.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"in_dir path not found",
));
}
if !p.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"in_dir is not a directory",
));
}
p
},
| None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"in_dir is not set",
));
},
};
let file_size: usize = match self.file_size {
| Some(s) => s,
| None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"file_size is not set",
));
},
};
let total_chunks: usize = match self.total_chunks {
| Some(s) => s,
| None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"total_chunks is not set",
));
},
};
let mut actual_size: usize = 0;
let mut missing: Vec<usize> = Vec::new();
for i in 0..total_chunks {
let target_file: PathBuf = in_dir.join(i.to_string());
if !target_file.exists() || !target_file.is_file() {
missing.push(i);
continue;
}
actual_size += fs::OpenOptions::new()
.read(true)
.open(&target_file)?
.metadata()?
.len() as usize;
}
if !missing.is_empty() {
return Ok(CheckResult {
success: false,
error: Some(CheckResultError {
error_type: CheckResultErrorType::Missing,
message: "Missing chunk(s)".to_string(),
missing: Some(missing),
}),
});
}
if actual_size != file_size {
return Ok(CheckResult {
success: false,
error: Some(CheckResultError {
error_type: CheckResultErrorType::Size,
message:
"the size of chunks is not equal to file_size parameter"
.to_string(),
missing: None,
}),
});
}
Ok(CheckResult { success: true, error: None })
}
}
impl Default for Check {
fn default() -> Self {
Self::new()
}
}