feat(backend): load vehicle translation + icon caches
This commit is contained in:
@@ -29,6 +29,8 @@ struct AppState {
|
|||||||
battles_db: PathBuf,
|
battles_db: PathBuf,
|
||||||
teams_db: PathBuf,
|
teams_db: PathBuf,
|
||||||
leaderboard_cache: Mutex<Option<CachedLeaderboard>>,
|
leaderboard_cache: Mutex<Option<CachedLeaderboard>>,
|
||||||
|
vehicle_names: HashMap<String, HashMap<String, String>>,
|
||||||
|
vehicle_icons: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CachedLeaderboard {
|
struct CachedLeaderboard {
|
||||||
@@ -317,7 +319,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
battles_db: resolve_db_path("TSS_BATTLES_DB", "tss_battles.db"),
|
battles_db: resolve_db_path("TSS_BATTLES_DB", "tss_battles.db"),
|
||||||
teams_db: resolve_db_path("TSS_TEAMS_DB", "tss_teams.db"),
|
teams_db: resolve_db_path("TSS_TEAMS_DB", "tss_teams.db"),
|
||||||
leaderboard_cache: Mutex::new(None),
|
leaderboard_cache: Mutex::new(None),
|
||||||
|
vehicle_names: load_vehicle_names(&resolve_db_path(
|
||||||
|
"VEHICLE_TRANSLATIONS_JSON",
|
||||||
|
"vehicle_translations.json",
|
||||||
|
)),
|
||||||
|
vehicle_icons: load_vehicle_icons(&resolve_db_path(
|
||||||
|
"VEHICLE_DATA_CACHE_JSON",
|
||||||
|
"vehicle_data_cache_all.json",
|
||||||
|
)),
|
||||||
});
|
});
|
||||||
|
tracing::info!(
|
||||||
|
"loaded {} vehicle name maps, {} vehicle icons",
|
||||||
|
state.vehicle_names.len(),
|
||||||
|
state.vehicle_icons.len()
|
||||||
|
);
|
||||||
spawn_leaderboard_refresh(state.clone());
|
spawn_leaderboard_refresh(state.clone());
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
@@ -1621,6 +1636,54 @@ fn allowed_origins() -> AllowOrigin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_vehicle_names(path: &FsPath) -> HashMap<String, HashMap<String, String>> {
|
||||||
|
match fs::read_to_string(path) {
|
||||||
|
Ok(s) => serde_json::from_str(&s).unwrap_or_default(),
|
||||||
|
Err(_) => HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_vehicle_icons(path: &FsPath) -> HashMap<String, String> {
|
||||||
|
// vehicle_data_cache_all.json is [[cdk, name, icon, tags], ...]
|
||||||
|
let raw: Vec<serde_json::Value> = match fs::read_to_string(path) {
|
||||||
|
Ok(s) => serde_json::from_str(&s).unwrap_or_default(),
|
||||||
|
Err(_) => Vec::new(),
|
||||||
|
};
|
||||||
|
let mut out = HashMap::new();
|
||||||
|
for entry in raw {
|
||||||
|
if let (Some(cdk), Some(icon)) = (
|
||||||
|
entry.get(0).and_then(|v| v.as_str()),
|
||||||
|
entry.get(2).and_then(|v| v.as_str()),
|
||||||
|
) {
|
||||||
|
out.insert(cdk.to_string(), icon.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_vehicle_name(
|
||||||
|
names: &HashMap<String, HashMap<String, String>>,
|
||||||
|
cdk: &str,
|
||||||
|
lang: &str,
|
||||||
|
) -> String {
|
||||||
|
if let Some(by_lang) = names.get(cdk) {
|
||||||
|
if let Some(n) = by_lang.get(lang) {
|
||||||
|
return n.clone();
|
||||||
|
}
|
||||||
|
if let Some(n) = by_lang.get("en") {
|
||||||
|
return n.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cdk.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup_vehicle_icon(icons: &HashMap<String, String>, cdk: &str) -> String {
|
||||||
|
icons
|
||||||
|
.get(cdk)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| format!("{cdk}.png"))
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_db_path(env_key: &str, default_file: &str) -> PathBuf {
|
fn resolve_db_path(env_key: &str, default_file: &str) -> PathBuf {
|
||||||
let raw = env::var(env_key).unwrap_or_else(|_| default_file.to_string());
|
let raw = env::var(env_key).unwrap_or_else(|_| default_file.to_string());
|
||||||
let expanded = expand_home(&raw);
|
let expanded = expand_home(&raw);
|
||||||
@@ -1702,3 +1765,43 @@ fn load_env_file(path: &FsPath) {
|
|||||||
env::set_var(key, value);
|
env::set_var(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vehicle_name_prefers_lang_then_en_then_cdk() {
|
||||||
|
let mut names = HashMap::new();
|
||||||
|
let mut t = HashMap::new();
|
||||||
|
t.insert("en".to_string(), "T-34".to_string());
|
||||||
|
t.insert("ru".to_string(), "Т-34".to_string());
|
||||||
|
names.insert("ussr_t_34".to_string(), t);
|
||||||
|
assert_eq!(lookup_vehicle_name(&names, "ussr_t_34", "ru"), "Т-34");
|
||||||
|
assert_eq!(lookup_vehicle_name(&names, "ussr_t_34", "de"), "T-34");
|
||||||
|
assert_eq!(lookup_vehicle_name(&names, "unknown_cdk", "en"), "unknown_cdk");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vehicle_icon_falls_back_to_cdk_png() {
|
||||||
|
let mut icons = HashMap::new();
|
||||||
|
icons.insert("ussr_t_34".to_string(), "ussr_t_34.png".to_string());
|
||||||
|
assert_eq!(lookup_vehicle_icon(&icons, "ussr_t_34"), "ussr_t_34.png");
|
||||||
|
assert_eq!(lookup_vehicle_icon(&icons, "germ_pz"), "germ_pz.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_vehicle_icons_parses_cache_array() {
|
||||||
|
let dir = std::env::temp_dir();
|
||||||
|
let p = dir.join("test_vehicle_cache_all.json");
|
||||||
|
fs::write(
|
||||||
|
&p,
|
||||||
|
r#"[["ussr_t_34","T-34","ussr_t_34.png",{}],["germ_pz","Pz","germ_pz.png",{}]]"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let icons = load_vehicle_icons(&p);
|
||||||
|
assert_eq!(icons.get("ussr_t_34").unwrap(), "ussr_t_34.png");
|
||||||
|
assert_eq!(icons.len(), 2);
|
||||||
|
let _ = fs::remove_file(&p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user