use digest::Digest;
use hex;
use hmac::{Hmac, KeyInit, Mac};
use md5::Md5;
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use serde::Serialize;
use sha1::Sha1;
use sha2::{Sha256, Sha512};
use std::io::Read;
use std::sync::Mutex;
use std::time::Duration;
use tauri::Emitter;
type HmacSha256 = Hmac<Sha256>;
#[derive(Serialize, Clone)]
pub struct HashResult {
pub md5: String,
pub sha1: String,
pub sha256: String,
pub sha512: String,
pub blake3: String,
}
static FILE_WATCHER: Mutex<Option<RecommendedWatcher>> = Mutex::new(None);
fn compute_hashes_from_bytes(data: &[u8]) -> HashResult {
let md5_hash = hex::encode(Md5::digest(data));
let sha1_hash = hex::encode(Sha1::digest(data));
let sha256_hash = hex::encode(Sha256::digest(data));
let sha512_hash = hex::encode(Sha512::digest(data));
let blake3_hash = blake3::hash(data).to_string();
HashResult {
md5: md5_hash,
sha1: sha1_hash,
sha256: sha256_hash,
sha512: sha512_hash,
blake3: blake3_hash,
}
}
fn compute_hashes_from_file(path: &str) -> Result<HashResult, String> {
let mut file = std::fs::File::open(path).map_err(|e| e.to_string())?;
let mut md5_h = Md5::new();
let mut sha1_h = Sha1::new();
let mut sha256_h = Sha256::new();
let mut sha512_h = Sha512::new();
let mut blake3_h = blake3::Hasher::new();
let mut buf = [0u8; 65536];
loop {
let n = file.read(&mut buf).map_err(|e| e.to_string())?;
if n == 0 {
break;
}
digest::Update::update(&mut md5_h, &buf[..n]);
digest::Update::update(&mut sha1_h, &buf[..n]);
digest::Update::update(&mut sha256_h, &buf[..n]);
digest::Update::update(&mut sha512_h, &buf[..n]);
blake3_h.update(&buf[..n]);
}
Ok(HashResult {
md5: hex::encode(md5_h.finalize()),
sha1: hex::encode(sha1_h.finalize()),
sha256: hex::encode(sha256_h.finalize()),
sha512: hex::encode(sha512_h.finalize()),
blake3: blake3_h.finalize().to_string(),
})
}
#[tauri::command]
pub fn generate_hashes(text: String) -> Result<HashResult, String> {
Ok(compute_hashes_from_bytes(text.as_bytes()))
}
#[tauri::command]
pub fn hash_file(file_path: String, app_handle: tauri::AppHandle) -> Result<HashResult, String> {
{
let mut guard = FILE_WATCHER.lock().map_err(|e| e.to_string())?;
*guard = None;
}
let hashes = compute_hashes_from_file(&file_path)?;
let watch_path = file_path.clone();
let app = app_handle.clone();
let (tx, rx) = std::sync::mpsc::channel::<notify::Result<Event>>();
let mut watcher = RecommendedWatcher::new(tx, Config::default()).map_err(|e| e.to_string())?;
watcher
.watch(
std::path::Path::new(&file_path),
RecursiveMode::NonRecursive,
)
.map_err(|e| e.to_string())?;
std::thread::spawn(move || {
let mut last_emit = std::time::Instant::now();
let debounce = Duration::from_millis(300);
for event in rx.into_iter().flatten() {
if matches!(event.kind, EventKind::Modify(_)) {
let now = std::time::Instant::now();
if now.duration_since(last_emit) >= debounce {
last_emit = now;
if let Ok(h) = compute_hashes_from_file(&watch_path) {
let _ = app.emit("file-hash-update", &h);
}
}
}
}
});
let mut guard = FILE_WATCHER.lock().map_err(|e| e.to_string())?;
*guard = Some(watcher);
Ok(hashes)
}
#[tauri::command]
pub fn stop_file_watch() -> Result<(), String> {
let mut guard = FILE_WATCHER.lock().map_err(|e| e.to_string())?;
*guard = None;
Ok(())
}
#[tauri::command]
pub fn compute_hmac(message: String, secret: String) -> Result<String, String> {
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).map_err(|e| e.to_string())?;
mac.update(message.as_bytes());
Ok(hex::encode(mac.finalize().into_bytes()))
}