hrrr 0.10.0

A fast native HRRR forecast-field viewer
use anyhow::Result;

mod app;
mod basemap;
mod basemap_artifact;
mod cache;
mod config;
mod decode;
mod fold_ui;
mod host;
mod library;
mod library_ui;
mod map;
mod model;
mod persist;
mod source;
mod spec;
mod state;
mod tray;
mod vector_map;
mod view;
mod witness;
mod worker;
mod xdg;

fn main() -> Result<()> {
    cleanse_relative_xdg()?;
    let mut arguments = std::env::args_os().skip(1);
    let Some(command) = arguments.next() else {
        return run_gui();
    };
    match command.to_str() {
        Some("--help" | "-h" | "help") => {
            print_help();
            Ok(())
        }
        Some("--version" | "-V") => {
            println!("hrrr {}", env!("CARGO_PKG_VERSION"));
            Ok(())
        }
        Some("basemap") => run_basemap(arguments),
        Some(other) => anyhow::bail!("unknown command `{other}`; run `hrrr --help`"),
        None => anyhow::bail!("command is not valid UTF-8"),
    }
}

fn run_gui() -> Result<()> {
    let ctx = egui::Context::default();
    dwemer_poolrooms::chrome::install(&ctx);
    let trace = eternalist_apps::TraceGuard::arm()?;
    let result = host::run(ctx);
    trace.flush();
    result
}

fn run_basemap(mut arguments: impl Iterator<Item = std::ffi::OsString>) -> Result<()> {
    let operation = arguments
        .next()
        .and_then(|argument| argument.into_string().ok())
        .unwrap_or_else(|| "status".to_owned());
    let lair = xdg::Lair::claim()?;
    match operation.as_str() {
        "install" => {
            let day = arguments.next();
            if arguments.next().is_some() {
                anyhow::bail!("usage: hrrr basemap install [YYYYMMDD]");
            }
            let day = day
                .as_deref()
                .map(|day| {
                    day.to_str()
                        .ok_or_else(|| anyhow::anyhow!("basemap date is not valid UTF-8"))
                })
                .transpose()?;
            let _installed = basemap_artifact::install(&lair, day)?;
            Ok(())
        }
        "status" if arguments.next().is_none() => basemap_artifact::status(&lair),
        "remove" if arguments.next().is_none() => basemap_artifact::remove(&lair),
        _ => anyhow::bail!(
            "unknown basemap operation `{operation}`; expected install, status, or remove"
        ),
    }
}

fn print_help() {
    println!(
        "\
HRRR native forecast-field viewer

Usage:
  hrrr
  hrrr basemap install [YYYYMMDD]
  hrrr basemap status
  hrrr basemap remove
  hrrr --help
  hrrr --version

The GUI reads NOAA HRRR forecast fields directly. `basemap install` explicitly
downloads the North American Protomaps basemap through zoom 12."
    );
}

fn cleanse_relative_xdg() -> Result<()> {
    const ROOTS: [&str; 5] = [
        "XDG_CACHE_HOME",
        "XDG_CONFIG_HOME",
        "XDG_DATA_HOME",
        "XDG_STATE_HOME",
        "XDG_RUNTIME_DIR",
    ];
    let invalid = ROOTS
        .into_iter()
        .filter(|name| {
            std::env::var_os(name).is_some_and(|value| !std::path::Path::new(&value).is_absolute())
        })
        .collect::<Vec<_>>();
    if invalid.is_empty() {
        return Ok(());
    }
    reexec_without(&invalid)
}

#[cfg(unix)]
fn reexec_without(names: &[&str]) -> Result<()> {
    use std::os::unix::process::CommandExt as _;

    let executable = std::env::current_exe()?;
    let mut command = std::process::Command::new(executable);
    let _command = command.args(std::env::args_os().skip(1));
    for name in names {
        let _command = command.env_remove(name);
    }
    Err(command.exec().into())
}

#[cfg(not(unix))]
fn reexec_without(_names: &[&str]) -> Result<()> {
    Ok(())
}