geolite 0.0.6

open source geocode solution
use crate::domain::osm_pbf_file::download::{download_event, md5_status, run};
use crate::domain::osm_pbf_file::origin;
use crate::domain::table;
use indicatif::{ProgressBar, ProgressStyle};
use std::io::Write;
use std::sync::{Arc, OnceLock};
use std::time::Instant;

pub fn command_handler_osm_pbf_file_download(
  data_path: &str,
  threads: &u8,
  sqlite_path: &str,
  inputs: &[String],
  ls_endpoint: Option<&str>,
  abort_on_any_error: bool,
) {
  for (i, input) in inputs.iter().enumerate() {
    if i > 0 {
      println!();
    }

    let is_url = input.starts_with("http://") || input.starts_with("https://");
    let from = if is_url {
      origin::url(input.clone())
    } else {
      print!("\x1b[1;32mresolving\x1b[0m url for '{input}'...");
      let _ = std::io::stdout().flush();
      let resolved = {
        let conn = crate::database::open_write(sqlite_path);
        crate::domain::osm_pbf_file::osm_pbf_file::open(Some(&conn), data_path)
          .resolve_geofabrik_url(input, ls_endpoint)
      };
      match resolved {
        Some(u) => {
          println!(" done");
          origin::geofabrik {
            id: input.clone(),
            url: u,
          }
        }
        None => {
          eprintln!(
            "\n\x1b[1;31merror\x1b[0m: '{input}' is not a valid url nor a known geofabrik id"
          );
          if abort_on_any_error {
            std::process::exit(1);
          }
          continue;
        }
      }
    };

    let url = from
      .download_url()
      .expect("a download origin has a url")
      .to_string();
    println!("\x1b[1;32mfetching\x1b[0m {url}");

    let pb: Arc<OnceLock<ProgressBar>> = Arc::new(OnceLock::new());
    let start_time: Arc<OnceLock<Instant>> = Arc::new(OnceLock::new());
    let fname = Arc::new(
      url
        .split('/')
        .next_back()
        .unwrap_or("file.osm.pbf")
        .to_string(),
    );

    let pb_cb = pb.clone();
    let start_cb = start_time.clone();
    let fname_cb = fname.clone();

    let output = run(data_path, &url, *threads, move |event| match event {
      download_event::download_start { total, .. } => {
        let _ = start_cb.set(Instant::now());
        let b = ProgressBar::new(total);
        b.set_draw_target(crate::cli::progress_draw_target());
        b.set_style(
          ProgressStyle::with_template(
            "{prefix:.bold.green} {msg:<40}  [{bar:20.green/white}] {percent:>3}%  {bytes:>10} / {total_bytes:<10}  {binary_bytes_per_sec}",
          )
          .unwrap()
          .progress_chars("=> "),
        );
        b.set_prefix("downloading");
        b.set_message((*fname_cb).clone());
        let _ = pb_cb.set(b);
      }
      download_event::download_progress { delta } => {
        if let Some(bar) = pb_cb.get() {
          bar.inc(delta);
        }
      }
      download_event::merging => {
        if let Some(bar) = pb_cb.get() {
          bar.finish();
        }
        print!("\x1b[1;32mmerging\x1b[0m parts...");
        let _ = std::io::stdout().flush();
      }
      download_event::merge_progress { .. } => {}
      download_event::verifying_md5 => {
        println!(" done");
        print!("\x1b[1;32mverifying\x1b[0m md5...");
        let _ = std::io::stdout().flush();
      }
      download_event::file_already_exists { path } => {
        println!(
          "\x1b[1;33mwarning\x1b[0m: file already exists at {} — reusing",
          path.display()
        );
      }
    });

    // never None: download::run panics on any transport failure before returning.
    let output = output.expect("download produced no output");

    let fname = output
      .path
      .file_name()
      .unwrap_or_default()
      .to_string_lossy();
    match start_time.get() {
      Some(t) => {
        let elapsed = t.elapsed().as_secs_f64();
        println!(" done");
        println!(
          "\x1b[1;32msaved\x1b[0m {fname} in {elapsed:.1}s → {}",
          output.path.display()
        );
      }
      None => {
        println!(
          "\x1b[1;32mreused\x1b[0m {fname} → {}",
          output.path.display()
        );
      }
    }
    if let md5_status::mismatch { expected, actual } = &output.md5 {
      eprintln!("\x1b[1;33mwarning\x1b[0m: md5 mismatch: expected {expected} got {actual}");
    }
    let conn = crate::database::open_write(sqlite_path);
    crate::domain::osm_pbf_file::osm_pbf_files::create_indexes(&conn);
    crate::domain::osm_pbf_file::repository::update_downloaded(
      &conn,
      &from,
      output.path.to_str().unwrap_or(""),
      output.total_bytes,
      &output.actual_md5,
    );
  }
}

#[cfg(test)]
#[path = "download.test.rs"]
mod tests;