use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;
use std::process::ExitCode;
use vin_decode::FstMap;
use vin_decode::data::{LookupRow, MakeRow, ModelRow, SchemaRow};
fn main() -> ExitCode {
let mut args = std::env::args().skip(1);
let Some(in_dir) = args.next() else {
eprintln!("usage: vin-decode-resolve-fks <in_dir> <out_dir> <nhtsa_lookups_dir>");
return ExitCode::from(2);
};
let Some(out_dir) = args.next() else {
eprintln!("usage: vin-decode-resolve-fks <in_dir> <out_dir> <nhtsa_lookups_dir>");
return ExitCode::from(2);
};
let Some(lookups_dir) = args.next() else {
eprintln!("usage: vin-decode-resolve-fks <in_dir> <out_dir> <nhtsa_lookups_dir>");
return ExitCode::from(2);
};
let in_dir = PathBuf::from(in_dir);
let out_dir = PathBuf::from(out_dir);
let lookups_dir = PathBuf::from(lookups_dir);
let resolvers = match load_resolvers(&lookups_dir) {
Ok(r) => r,
Err(e) => {
eprintln!("loading lookup tables failed: {e}");
return ExitCode::from(1);
}
};
eprintln!("loaded {} element resolvers", resolvers.len());
let map: FstMap<LookupRow> = match FstMap::open(&in_dir) {
Ok(m) => m,
Err(e) => {
eprintln!("opening schema_lookup at {} failed: {e}", in_dir.display());
return ExitCode::from(1);
}
};
let keys = map.keys();
eprintln!("schema groups: {}", keys.len());
let mut resolved_rows = 0usize;
let mut total_rows = 0usize;
let mut groups: Vec<(String, Vec<LookupRow>)> = Vec::with_capacity(keys.len());
for key in keys {
let Some(rows) = map.get(&key) else {
continue;
};
let mut new_rows: Vec<LookupRow> = Vec::with_capacity(rows.len());
for row in rows {
total_rows += 1;
let mut new_value = row.value.clone();
if let Some(resolver) = resolvers.get(row.element.as_str()) {
if let Ok(id) = row.value.parse::<u32>() {
if let Some(name) = resolver.get(&id) {
new_value = name.clone();
resolved_rows += 1;
}
}
}
new_rows.push(LookupRow {
pattern: row.pattern,
element: row.element,
value: new_value,
weight: row.weight,
});
}
groups.push((key, new_rows));
}
groups.sort_by(|a, b| a.0.cmp(&b.0));
std::fs::create_dir_all(&out_dir).ok();
if let Err(e) = vin_decode::build::write_grouped(
&groups,
&out_dir.join("schema_lookup.fst"),
&out_dir.join("schema_lookup.bin"),
) {
eprintln!("write_grouped failed: {e}");
return ExitCode::from(1);
}
eprintln!(
"resolved {resolved_rows}/{total_rows} rows ({:.1}%)",
resolved_rows as f64 * 100.0 / total_rows.max(1) as f64
);
if let Err(e) = regen_make_models(&in_dir, &groups, &out_dir) {
eprintln!("regen_make_models failed: {e}");
return ExitCode::from(1);
}
ExitCode::SUCCESS
}
fn regen_make_models(
in_dir: &std::path::Path,
schema_lookup: &[(String, Vec<LookupRow>)],
out_dir: &std::path::Path,
) -> Result<(), Box<dyn std::error::Error>> {
let wmi_make_map: FstMap<MakeRow> = FstMap::open(in_dir)?;
let wmi_schema_map: FstMap<SchemaRow> = FstMap::open(in_dir)?;
let wmi_make: Vec<(String, Vec<MakeRow>)> = wmi_make_map
.keys()
.into_iter()
.filter_map(|k| wmi_make_map.get(&k).map(|v| (k, v)))
.collect();
let wmi_schema: Vec<(String, Vec<SchemaRow>)> = wmi_schema_map
.keys()
.into_iter()
.filter_map(|k| wmi_schema_map.get(&k).map(|v| (k, v)))
.collect();
let make_models = vin_decode::build::derive_make_models(&wmi_make, &wmi_schema, schema_lookup);
vin_decode::build::write_grouped(
&make_models,
&out_dir.join("make_models.fst"),
&out_dir.join("make_models.bin"),
)?;
eprintln!("regen make_models: {} make groups", make_models.len());
let _ = std::any::type_name::<ModelRow>();
Ok(())
}
fn load_resolvers(dir: &std::path::Path) -> std::io::Result<HashMap<String, HashMap<u32, String>>> {
let mut out: HashMap<String, HashMap<u32, String>> = HashMap::new();
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("json") {
continue;
}
let element = match path.file_stem().and_then(|s| s.to_str()) {
Some(n) => n.to_string(),
None => continue,
};
let f = File::open(&path)?;
let json: serde_json::Value = serde_json::from_reader(f).map_err(std::io::Error::other)?;
let mut map: HashMap<u32, String> = HashMap::new();
if let Some(arr) = json.get("Results").and_then(|v| v.as_array()) {
for r in arr {
let id = r.get("Id").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let name = r
.get("Name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if id > 0 && !name.is_empty() {
map.insert(id, name);
}
}
}
if !map.is_empty() {
out.insert(element, map);
}
}
Ok(out)
}