use anyhow::Result;
use path_absolutize::Absolutize;
use regex::Regex;
use std::path::Path;
use url::Url;
use crate::{
app,
color_templates::{ERROR_TEMPLATE, INFO_TEMPLATE, WARN_TEMPLATE_NO_BG_COLOR},
command_handling::{CommandResult, HashCompareResult},
hasher::Algorithm,
os_specifics::{self, OS},
};
pub const CAPACITY: usize = 64 * 1024;
pub const BOUNCING_BAR: [&str; 16] = [
"[ ]", "[= ]", "[== ]", "[=== ]", "[====]", "[ ===]", "[ ==]", "[ =]", "[ ]",
"[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]",
];
const KIB: f64 = 1024.0;
const MIB: f64 = KIB * KIB;
const GIB: f64 = KIB * MIB;
const TIB: f64 = KIB * GIB;
pub fn processing_cmd_result(cmd_result: &CommandResult) -> Result<()> {
let hash_source = match &cmd_result.file_location {
Some(file_location) => absolute_path_as_string(file_location),
None => match &cmd_result.buffer {
Some(buffer) => format!("Buffer of size {} byte(s)", buffer.len()),
None => "Buffer of unknown size".to_string(),
},
};
println!(
"\n{} : {}",
WARN_TEMPLATE_NO_BG_COLOR.output("Input source"),
hash_source
);
print_hash_result(
cmd_result.hash_compare_result.as_ref(),
cmd_result.used_algorithm,
&cmd_result.calculated_hash_sum,
);
save_calculated_hash_sum(cmd_result)?;
Ok(())
}
fn print_hash_result(
hash_to_compare: Option<&HashCompareResult>,
used_algorithm: Algorithm,
calculated_hash_sum: &str,
) {
let calculated_hash_sum = format!("Calculated hash sum: {calculated_hash_sum}");
log::info!("{calculated_hash_sum}");
println!("{calculated_hash_sum}");
if let Some(hash_to_compare) = hash_to_compare {
let origin_hash = format!(
"Origin hash sum : {}",
hash_to_compare.origin_hash_sum.to_ascii_lowercase()
);
log::info!("{origin_hash}");
println!("{origin_hash}");
if !hash_to_compare.is_hash_equal {
println!(
"\n{} - Used hash algorithm: {}",
ERROR_TEMPLATE.output("Hash sums DO NOT match"),
WARN_TEMPLATE_NO_BG_COLOR.output(used_algorithm)
);
} else {
println!(
"\n{} - Used hash algorithm: {}",
INFO_TEMPLATE.output("Hash sums match"),
WARN_TEMPLATE_NO_BG_COLOR.output(used_algorithm)
);
}
} else {
println!(
"\n- Used hash algorithm: {}",
WARN_TEMPLATE_NO_BG_COLOR.output(used_algorithm)
);
}
}
fn save_calculated_hash_sum(cmd_result: &CommandResult) -> Result<()> {
if cmd_result.save {
let app_data_dir = app::data_dir();
let (file_name, content) = if let Some(file_path) = &cmd_result.file_location {
let prefix = file_path
.file_name()
.unwrap_or(std::ffi::OsStr::new("hash_sum"))
.to_string_lossy();
(
format!(
"{}.{}",
prefix,
cmd_result.used_algorithm.to_string().to_lowercase()
),
format!("{}\t{}", cmd_result.calculated_hash_sum, prefix),
)
} else {
(
format!(
"hash_sum.{}",
cmd_result.used_algorithm.to_string().to_lowercase()
),
format!(
"{}\t{}",
cmd_result.calculated_hash_sum,
cmd_result.buffer.as_deref().unwrap_or_default()
),
)
};
let hash_sum_file_path = app_data_dir.join(file_name);
std::fs::write(hash_sum_file_path, content)?;
}
Ok(())
}
pub fn calc_duration(seconds: u64) -> String {
let hours = seconds / 3600;
let minutes = (seconds % 3600) / 60;
let remaining_seconds = seconds % 60;
if hours > 0 {
format!("{hours}h {minutes}m {remaining_seconds}s")
} else if minutes > 0 {
format!("{minutes}m {remaining_seconds}s")
} else if remaining_seconds < 1 {
"< 1s".to_string()
} else {
format!("{remaining_seconds}s")
}
}
pub fn is_valid_url(url: &str) -> bool {
match Url::parse(url) {
Ok(url) => {
!url.scheme().is_empty()
&& (matches!(url.scheme(), "http" | "https"))
&& url.has_host()
&& !url.path().is_empty()
}
Err(_) => false,
}
}
pub fn extract_file_name_from_url(url: &str) -> Option<String> {
Url::parse(url)
.ok()?
.path()
.split('/')
.next_back()
.and_then(|file_name| {
if file_name.trim().is_empty() {
None
} else {
Some(file_name.to_string())
}
})
}
pub fn extract_file_name(url: &str, content_disposition: &str, os_type: &OS) -> Option<String> {
let filename = extract_filename_from_content_disposition(content_disposition)
.or_else(|| extract_file_name_from_url(url));
filename
.map(|f| decode_percent_encoded_to_utf_8(&f))
.map(|f| replace_invalid_chars_with_underscore(&f, os_type))
}
pub fn extract_filename_from_content_disposition(header_value: &str) -> Option<String> {
if !header_value.to_lowercase().starts_with("attachment;") || header_value.trim().is_empty() {
return None;
}
let filename_prefixes = ["filename*=", "filename="];
let utf8_regex = Regex::new(r"(?i)utf-8").unwrap();
for part in header_value.split(';').map(str::trim) {
for prefix in &filename_prefixes {
if let Some(filename) = part.strip_prefix(prefix) {
let filename = utf8_regex
.replace_all(filename, "")
.trim_matches(|c| matches!(c, ' ' | '\t' | '\n' | '\r' | '"' | '\''))
.to_string();
if !filename.is_empty() {
return Some(filename);
}
}
}
}
None
}
pub fn decode_percent_encoded_to_utf_8(input: &str) -> String {
percent_encoding::percent_decode_str(input)
.decode_utf8()
.unwrap_or(std::borrow::Cow::Borrowed(input))
.to_string()
}
pub fn replace_invalid_chars_with_underscore(filename: &str, os_type: &OS) -> String {
let invalid_chars = match os_type {
OS::Linux | OS::MacOs => os_specifics::UNIX_INVALID_FILE_NAME_CHARS,
OS::Windows => os_specifics::WINDOWS_INVALID_FILE_NAME_CHARS,
};
filename
.chars()
.map(|c| if invalid_chars.contains(c) { '_' } else { c })
.collect::<String>()
}
pub fn absolute_path_as_string(path: &Path) -> String {
match path.absolutize() {
Ok(absolute_path) => absolute_path.display().to_string(),
Err(_) => path.display().to_string(),
}
}
pub fn convert_bytes_to_human_readable(bytes: usize) -> String {
if bytes < KIB as usize {
format!("{bytes} B")
} else if bytes < MIB as usize {
format!("{:.2} KiB", bytes as f64 / KIB)
} else if bytes < GIB as usize {
format!("{:.2} MiB", bytes as f64 / MIB)
} else if bytes < TIB as usize {
format!("{:.2} GiB", bytes as f64 / GIB)
} else {
format!("{:.2} TiB", bytes as f64 / TIB)
}
}