use super::download::{self, download_file_async, download_to_string};
use crate::utils::datadir;
use serde_json::Value;
use std::path::PathBuf;
use std::thread::JoinHandle;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("Expected JSON array of file URLs")]
NotJsonArray,
#[error("Expected string URL")]
NotJsonString,
#[error("Invalid JSON manifest entry")]
InvalidManifestEntry,
#[error("Could not parse manifest entries")]
ManifestParseFailed,
#[error(
"Data directory is read-only. Try setting SATKIT_DATA environment variable \
to a writeable directory and re-starting"
)]
DataDirReadOnly,
#[error("Background download thread panicked")]
ThreadPanic,
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Datadir(#[from] crate::utils::datadir::Error),
#[error(transparent)]
Download(#[from] download::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
fn download_from_url_json(json_url: String, basedir: &std::path::Path) -> Result<()> {
let json_base: Value = serde_json::from_str(download_to_string(json_url.as_str())?.as_str())?;
let arr = json_base.as_array().ok_or(Error::NotJsonArray)?;
let vresult: Vec<JoinHandle<download::Result<bool>>> = arr
.iter()
.map(|url| -> Result<JoinHandle<download::Result<bool>>> {
let url_str = url.as_str().ok_or(Error::NotJsonString)?;
Ok(download_file_async(url_str.to_string(), basedir, true))
})
.collect::<Result<Vec<_>>>()?;
for jh in vresult {
jh.join().map_err(|_| Error::ThreadPanic)??;
}
Ok(())
}
fn download_from_json(
v: &Value,
basedir: std::path::PathBuf,
baseurl: String,
overwrite: &bool,
thandles: &mut Vec<JoinHandle<download::Result<bool>>>,
) -> Result<()> {
if let Some(obj) = v.as_object() {
let r1: Vec<Result<()>> = obj
.iter()
.map(|(key, val)| -> Result<()> {
let pbnew = basedir.join(key);
if !pbnew.is_dir() {
std::fs::create_dir_all(pbnew.clone())?;
}
let mut newurl = baseurl.clone();
newurl.push_str(format!("/{key}").as_str());
download_from_json(val, pbnew, newurl, overwrite, thandles)?;
Ok(())
})
.filter(|res| res.is_err())
.collect();
if !r1.is_empty() {
return Err(Error::ManifestParseFailed);
}
} else if let Some(arr) = v.as_array() {
let r2: Vec<Result<()>> = arr
.iter()
.map(|val| -> Result<()> {
download_from_json(val, basedir.clone(), baseurl.clone(), overwrite, thandles)?;
Ok(())
})
.filter(|res| res.is_err())
.collect();
if !r2.is_empty() {
return Err(Error::ManifestParseFailed);
}
} else if let Some(s) = v.as_str() {
let mut newurl = baseurl;
newurl.push_str(format!("/{s}").as_str());
thandles.push(download_file_async(newurl, &basedir, *overwrite));
} else {
return Err(Error::InvalidManifestEntry);
}
Ok(())
}
fn download_datadir(basedir: PathBuf, baseurl: String, overwrite: &bool) -> Result<()> {
if !basedir.is_dir() {
std::fs::create_dir_all(basedir.clone())?;
}
let mut fileurl = baseurl.clone();
fileurl.push_str("/files.json");
let json_base: Value = serde_json::from_str(download_to_string(fileurl.as_str())?.as_str())?;
let mut thandles: Vec<JoinHandle<download::Result<bool>>> = Vec::new();
download_from_json(&json_base, basedir, baseurl, overwrite, &mut thandles)?;
for jh in thandles {
jh.join().map_err(|_| Error::ThreadPanic)??;
}
Ok(())
}
pub fn update_datafiles(dir: Option<PathBuf>, overwrite_if_exists: bool) -> Result<()> {
let downloaddir = match dir {
Some(d) => d,
None => datadir()?,
};
if downloaddir.metadata()?.permissions().readonly() {
return Err(Error::DataDirReadOnly);
}
println!(
"Downloading data files to {}",
downloaddir.to_str().unwrap()
);
download_datadir(
downloaddir.clone(),
String::from("https://storage.googleapis.com/astrokit-astro-data"),
&overwrite_if_exists,
)?;
println!("Now downloading files that are regularly updated:");
println!(" Space Weather & Earth Orientation Parameters");
download_from_url_json(
String::from("https://storage.googleapis.com/astrokit-astro-data/files_refresh.json"),
&downloaddir,
)?;
println!(" Solar Cycle Forecast");
if let Err(e) = crate::solar_cycle_forecast::update() {
eprintln!("Warning: could not download solar cycle forecast: {e}");
}
Ok(())
}