|
1 | 1 | use crate::{debug_log, get_config_content}; |
2 | 2 | use serde::Deserialize; |
3 | 3 | use std::cmp::Ordering; |
4 | | -use std::collections::BinaryHeap; |
| 4 | +use std::collections::{BinaryHeap, HashSet}; |
5 | 5 | use std::fs; |
6 | 6 | use std::time::{SystemTime, UNIX_EPOCH}; |
7 | 7 | use tokio::time::{Duration, Instant}; |
@@ -369,3 +369,98 @@ pub async fn cache_loop(cache_dir: &str) { |
369 | 369 | tokio::time::sleep(Duration::from_secs(HOUSEKEEPING_INTERVAL_SECS)).await; |
370 | 370 | } |
371 | 371 | } |
| 372 | + |
| 373 | +fn remove_zero_sized_files(cache_dir: &str) -> u64 { |
| 374 | + let mut removed = 0; |
| 375 | + let entries = match fs::read_dir(cache_dir) { |
| 376 | + Ok(entries) => entries, |
| 377 | + Err(e) => { |
| 378 | + eprintln!("Error reading cache directory during zero-sized cleanup: {}", e); |
| 379 | + return 0; |
| 380 | + } |
| 381 | + }; |
| 382 | + |
| 383 | + for entry in entries.flatten() { |
| 384 | + let path = entry.path(); |
| 385 | + let metadata = match entry.metadata() { |
| 386 | + Ok(metadata) => metadata, |
| 387 | + Err(_) => continue, |
| 388 | + }; |
| 389 | + |
| 390 | + if !metadata.is_file() || metadata.len() != 0 { |
| 391 | + continue; |
| 392 | + } |
| 393 | + |
| 394 | + if let Err(e) = fs::remove_file(&path) { |
| 395 | + debug_log!("Failed to remove zero-sized file {:?}: {}", path, e); |
| 396 | + } else { |
| 397 | + removed += 1; |
| 398 | + } |
| 399 | + } |
| 400 | + |
| 401 | + removed |
| 402 | +} |
| 403 | + |
| 404 | +fn remove_orphan_files(cache_dir: &str) -> u64 { |
| 405 | + let entries = match fs::read_dir(cache_dir) { |
| 406 | + Ok(entries) => entries, |
| 407 | + Err(e) => { |
| 408 | + eprintln!("Error reading cache directory during orphan cleanup: {}", e); |
| 409 | + return 0; |
| 410 | + } |
| 411 | + }; |
| 412 | + |
| 413 | + let mut contents = HashSet::new(); |
| 414 | + let mut headers = HashSet::new(); |
| 415 | + |
| 416 | + for entry in entries.flatten() { |
| 417 | + let path = match entry.path().to_str() { |
| 418 | + Some(path) => path.to_string(), |
| 419 | + None => continue, |
| 420 | + }; |
| 421 | + |
| 422 | + if let Some(base) = path.strip_suffix(".content") { |
| 423 | + contents.insert(base.to_string()); |
| 424 | + } else if let Some(base) = path.strip_suffix(".headers") { |
| 425 | + headers.insert(base.to_string()); |
| 426 | + } |
| 427 | + } |
| 428 | + |
| 429 | + let mut removed = 0; |
| 430 | + |
| 431 | + for base in contents.difference(&headers) { |
| 432 | + let file = format!("{}.content", base); |
| 433 | + if let Err(e) = fs::remove_file(&file) { |
| 434 | + debug_log!("Failed to remove orphan content {}: {}", file, e); |
| 435 | + } else { |
| 436 | + removed += 1; |
| 437 | + } |
| 438 | + } |
| 439 | + |
| 440 | + for base in headers.difference(&contents) { |
| 441 | + let file = format!("{}.headers", base); |
| 442 | + if let Err(e) = fs::remove_file(&file) { |
| 443 | + debug_log!("Failed to remove orphan headers {}: {}", file, e); |
| 444 | + } else { |
| 445 | + removed += 1; |
| 446 | + } |
| 447 | + } |
| 448 | + |
| 449 | + removed |
| 450 | +} |
| 451 | + |
| 452 | +fn run_cache_validation(cache_dir: String) { |
| 453 | + let zero_removed = remove_zero_sized_files(&cache_dir); |
| 454 | + let orphan_removed = remove_orphan_files(&cache_dir); |
| 455 | + |
| 456 | + println!( |
| 457 | + "[cache-validation] removed {} zero-sized files and {} orphaned cache entries.", |
| 458 | + zero_removed, orphan_removed |
| 459 | + ); |
| 460 | +} |
| 461 | + |
| 462 | +pub async fn validate_cache(cache_dir: String) { |
| 463 | + if let Err(e) = tokio::task::spawn_blocking(move || run_cache_validation(cache_dir)).await { |
| 464 | + eprintln!("Cache validation task failed: {}", e); |
| 465 | + } |
| 466 | +} |
0 commit comments