Purify
This commit is contained in:
parent
1d3b9dddf5
commit
fa4dc716db
2 changed files with 161 additions and 21 deletions
102
src/rebuild.rs
102
src/rebuild.rs
|
|
@ -12,6 +12,25 @@ struct NginxEntry {
|
|||
size: Option<i64>,
|
||||
}
|
||||
|
||||
/// Pure: merge volume scan results into a unified index.
|
||||
/// Each scan is (volume_url, list of (key, size) pairs).
|
||||
/// Returns a map of key -> (volumes containing it, max size seen).
|
||||
pub fn merge_volume_scans(
|
||||
scans: &[(String, Vec<(String, i64)>)],
|
||||
) -> HashMap<String, (Vec<String>, i64)> {
|
||||
let mut index: HashMap<String, (Vec<String>, i64)> = HashMap::new();
|
||||
for (vol_url, keys) in scans {
|
||||
for (key, size) in keys {
|
||||
let entry = index.entry(key.clone()).or_insert_with(|| (Vec::new(), *size));
|
||||
entry.0.push(vol_url.clone());
|
||||
if *size > entry.1 {
|
||||
entry.1 = *size;
|
||||
}
|
||||
}
|
||||
}
|
||||
index
|
||||
}
|
||||
|
||||
async fn list_volume_keys(volume_url: &str) -> Result<Vec<(String, i64)>, String> {
|
||||
let http = reqwest::Client::new();
|
||||
let mut keys = Vec::new();
|
||||
|
|
@ -57,25 +76,23 @@ pub async fn run(args: &Args) {
|
|||
let _ = std::fs::remove_file(format!("{db_path}-shm"));
|
||||
|
||||
let db = db::Db::new(db_path);
|
||||
let mut index: HashMap<String, (Vec<String>, i64)> = HashMap::new();
|
||||
|
||||
// I/O: scan each volume
|
||||
let mut scans = Vec::new();
|
||||
for vol_url in &args.volumes {
|
||||
eprintln!("Scanning {vol_url}...");
|
||||
match list_volume_keys(vol_url).await {
|
||||
Ok(keys) => {
|
||||
eprintln!(" Found {} keys", keys.len());
|
||||
for (key, size) in keys {
|
||||
let entry = index.entry(key).or_insert_with(|| (Vec::new(), size));
|
||||
entry.0.push(vol_url.clone());
|
||||
if size > entry.1 {
|
||||
entry.1 = size;
|
||||
}
|
||||
}
|
||||
scans.push((vol_url.clone(), keys));
|
||||
}
|
||||
Err(e) => eprintln!(" Error scanning {vol_url}: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
// Pure: merge scan results
|
||||
let index = merge_volume_scans(&scans);
|
||||
|
||||
let records: Vec<_> = index
|
||||
.into_iter()
|
||||
.map(|(k, (v, s))| (k, v, Some(s)))
|
||||
|
|
@ -84,3 +101,72 @@ pub async fn run(args: &Args) {
|
|||
db.bulk_put(records).await.expect("bulk_put failed");
|
||||
eprintln!("Rebuilt index with {count} keys");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_merge_empty_scans() {
|
||||
let scans: Vec<(String, Vec<(String, i64)>)> = vec![];
|
||||
let index = merge_volume_scans(&scans);
|
||||
assert!(index.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_single_volume() {
|
||||
let scans = vec![(
|
||||
"http://vol1".to_string(),
|
||||
vec![
|
||||
("key1".to_string(), 100),
|
||||
("key2".to_string(), 200),
|
||||
],
|
||||
)];
|
||||
let index = merge_volume_scans(&scans);
|
||||
assert_eq!(index.len(), 2);
|
||||
assert_eq!(index.get("key1"), Some(&(vec!["http://vol1".to_string()], 100)));
|
||||
assert_eq!(index.get("key2"), Some(&(vec!["http://vol1".to_string()], 200)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_key_on_multiple_volumes() {
|
||||
let scans = vec![
|
||||
("http://vol1".to_string(), vec![("shared".to_string(), 100)]),
|
||||
("http://vol2".to_string(), vec![("shared".to_string(), 100)]),
|
||||
("http://vol3".to_string(), vec![("shared".to_string(), 100)]),
|
||||
];
|
||||
let index = merge_volume_scans(&scans);
|
||||
assert_eq!(index.len(), 1);
|
||||
let (volumes, size) = index.get("shared").unwrap();
|
||||
assert_eq!(volumes.len(), 3);
|
||||
assert!(volumes.contains(&"http://vol1".to_string()));
|
||||
assert!(volumes.contains(&"http://vol2".to_string()));
|
||||
assert!(volumes.contains(&"http://vol3".to_string()));
|
||||
assert_eq!(*size, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_takes_max_size() {
|
||||
// Same key with different sizes on different volumes (corruption or update race)
|
||||
let scans = vec![
|
||||
("http://vol1".to_string(), vec![("key".to_string(), 50)]),
|
||||
("http://vol2".to_string(), vec![("key".to_string(), 200)]),
|
||||
("http://vol3".to_string(), vec![("key".to_string(), 100)]),
|
||||
];
|
||||
let index = merge_volume_scans(&scans);
|
||||
let (_, size) = index.get("key").unwrap();
|
||||
assert_eq!(*size, 200, "should take maximum size across volumes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_disjoint_keys() {
|
||||
let scans = vec![
|
||||
("http://vol1".to_string(), vec![("a".to_string(), 10)]),
|
||||
("http://vol2".to_string(), vec![("b".to_string(), 20)]),
|
||||
];
|
||||
let index = merge_volume_scans(&scans);
|
||||
assert_eq!(index.len(), 2);
|
||||
assert_eq!(index.get("a").unwrap().0, vec!["http://vol1".to_string()]);
|
||||
assert_eq!(index.get("b").unwrap().0, vec!["http://vol2".to_string()]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue