use std::fs;
use std::path::{Path, PathBuf};
use crate::catalog_cmd;
pub fn default_db_path() -> PathBuf {
let managed = catalog_cmd::default_dir();
if catalog_cmd::REGISTRY
.iter()
.filter(|e| matches!(e.files, catalog_cmd::Files::AstapDb { .. }))
.any(|e| catalog_cmd::is_installed(&managed, e))
{
managed
} else {
PathBuf::from(".")
}
}
const DB_FOV_RANGES: &[(&str, f64, f64, u8)] = &[
("d80", 0.15, 6.0, 8),
("v50", 0.20, 6.0, 6),
("d50", 0.20, 6.0, 5),
("d20", 0.30, 6.0, 4),
("v05", 0.60, 6.0, 3),
("d05", 0.60, 6.0, 2),
("g05", 3.00, 20.0, 7),
("w08", 20.0, 80.0, 7),
];
fn db_prefix(file_name: &str) -> Option<&str> {
if catalog_cmd::ASTAP_EXTS
.iter()
.any(|(ext, _)| file_name.ends_with(ext))
{
file_name.split('_').next()
} else {
None
}
}
fn available_dbs(db_path: &Path) -> Vec<String> {
let mut out: Vec<String> = fs::read_dir(db_path)
.into_iter()
.flatten()
.flatten()
.filter_map(|e| db_prefix(&e.file_name().to_string_lossy()).map(str::to_string))
.collect();
out.sort();
out.dedup();
out
}
pub fn select_db_for_fov(db_path: &Path, fov_deg: f64) -> Option<String> {
select_from(&available_dbs(db_path), fov_deg)
}
fn select_from(present: &[String], fov_deg: f64) -> Option<String> {
if present.is_empty() {
return None;
}
let installed = DB_FOV_RANGES
.iter()
.filter(|(prefix, ..)| present.iter().any(|p| p == prefix));
let mut best: Option<(u8, &str)> = None;
for &(prefix, lo, hi, pref) in installed.clone() {
if fov_deg >= lo && fov_deg <= hi && best.is_none_or(|(bp, _)| pref > bp) {
best = Some((pref, prefix));
}
}
if let Some((_, prefix)) = best {
return Some(prefix.to_string());
}
let mut fallback: Option<(f64, &str)> = None;
for &(prefix, lo, hi, _) in installed {
let dist = if fov_deg < lo {
lo - fov_deg
} else {
fov_deg - hi
};
if fallback.is_none_or(|(bd, _)| dist < bd) {
fallback = Some((dist, prefix));
}
}
fallback
.map(|(_, p)| p.to_string())
.or_else(|| present.first().cloned())
}
#[cfg(test)]
mod tests {
use super::*;
fn owned(v: &[&str]) -> Vec<String> {
v.iter().map(|s| (*s).to_string()).collect()
}
#[test]
fn prefixes_come_from_every_database_format() {
assert_eq!(db_prefix("d50_0101.1476"), Some("d50"));
assert_eq!(db_prefix("g05_0101.290"), Some("g05"));
assert_eq!(db_prefix("w08_0101.001"), Some("w08"));
assert_eq!(db_prefix("index-4107.fits"), None);
assert_eq!(db_prefix("readme.txt"), None);
}
#[test]
fn the_densest_covering_database_wins() {
let all = owned(&["d05", "d50", "d80", "g05", "w08"]);
assert_eq!(select_from(&all, 1.0).as_deref(), Some("d80"));
assert_eq!(select_from(&all, 10.0).as_deref(), Some("g05"));
assert_eq!(select_from(&all, 40.0).as_deref(), Some("w08"));
assert_eq!(select_from(&all, 4.0).as_deref(), Some("d80"));
}
#[test]
fn an_uncovered_field_gets_the_nearest_range() {
let d = owned(&["d50", "g05"]);
assert_eq!(select_from(&d, 0.05).as_deref(), Some("d50"));
assert_eq!(select_from(&d, 30.0).as_deref(), Some("g05"));
}
#[test]
fn unknown_prefixes_are_a_last_resort() {
assert_eq!(select_from(&owned(&["h18"]), 1.0).as_deref(), Some("h18"));
assert_eq!(select_from(&[], 1.0), None);
}
}