use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
pub fn can_reprocess(
path: &Path,
last_processed: &HashMap<PathBuf, Instant>,
debounce: Duration,
) -> bool {
can_reprocess_at(path, last_processed, debounce, Instant::now())
}
pub fn can_reprocess_at(
path: &Path,
last_processed: &HashMap<PathBuf, Instant>,
debounce: Duration,
now: Instant,
) -> bool {
match last_processed.get(path) {
Some(last_time) => now.duration_since(*last_time) >= debounce,
None => true,
}
}
pub fn cleanup_old_entries(last_processed: &mut HashMap<PathBuf, Instant>, debounce: Duration) {
cleanup_old_entries_at(last_processed, debounce, Instant::now())
}
pub fn cleanup_old_entries_at(
last_processed: &mut HashMap<PathBuf, Instant>,
debounce: Duration,
now: Instant,
) {
let cutoff = now - debounce * 10;
last_processed.retain(|_, timestamp| *timestamp >= cutoff);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_reprocess_new_file() {
let last_processed: HashMap<PathBuf, Instant> = HashMap::new();
let path = Path::new("/test/file.rs");
assert!(can_reprocess(
path,
&last_processed,
Duration::from_millis(100)
));
}
#[test]
fn can_reprocess_after_debounce() {
let mut last_processed: HashMap<PathBuf, Instant> = HashMap::new();
let path = PathBuf::from("/test/file.rs");
last_processed.insert(path.clone(), Instant::now() - Duration::from_millis(200));
assert!(can_reprocess(
&path,
&last_processed,
Duration::from_millis(100)
));
}
#[test]
fn cannot_reprocess_within_debounce() {
let mut last_processed: HashMap<PathBuf, Instant> = HashMap::new();
let path = PathBuf::from("/test/file.rs");
last_processed.insert(path.clone(), Instant::now());
assert!(!can_reprocess(
&path,
&last_processed,
Duration::from_millis(100)
));
}
#[test]
fn cleanup_old_entries_removes_stale_entries() {
let mut last_processed: HashMap<PathBuf, Instant> = HashMap::new();
let old_path = PathBuf::from("/test/old.rs");
let recent_path = PathBuf::from("/test/recent.rs");
last_processed.insert(
old_path.clone(),
Instant::now() - Duration::from_millis(1500),
);
last_processed.insert(
recent_path.clone(),
Instant::now() - Duration::from_millis(50),
);
let debounce = Duration::from_millis(100);
cleanup_old_entries(&mut last_processed, debounce);
assert!(!last_processed.contains_key(&old_path));
assert!(last_processed.contains_key(&recent_path));
}
#[test]
fn cleanup_old_entries_preserves_recent_entries() {
let mut last_processed: HashMap<PathBuf, Instant> = HashMap::new();
let path1 = PathBuf::from("/test/file1.rs");
let path2 = PathBuf::from("/test/file2.rs");
last_processed.insert(path1.clone(), Instant::now() - Duration::from_millis(500));
last_processed.insert(path2.clone(), Instant::now() - Duration::from_millis(300));
let debounce = Duration::from_millis(100);
cleanup_old_entries(&mut last_processed, debounce);
assert!(last_processed.contains_key(&path1));
assert!(last_processed.contains_key(&path2));
}
#[test]
fn can_reprocess_exact_boundary() {
let mut last_processed: HashMap<PathBuf, Instant> = HashMap::new();
let path = PathBuf::from("/test/file.rs");
let debounce = Duration::from_millis(100);
last_processed.insert(path.clone(), Instant::now() - debounce);
assert!(
can_reprocess(&path, &last_processed, debounce),
"File should be reprocessable when exactly debounce duration has elapsed"
);
}
}