satkit/utils/
update_data.rs

1use super::download_file_async;
2use super::download_to_string;
3use crate::utils::datadir;
4use json::JsonValue;
5use std::path::PathBuf;
6use std::thread::JoinHandle;
7
8use anyhow::{bail, Result};
9
10/// Download a list of files from a JSON file
11fn download_from_url_json(json_url: String, basedir: &std::path::Path) -> Result<()> {
12    let json_base: JsonValue = json::parse(download_to_string(json_url.as_str())?.as_str())?;
13    let vresult: Vec<std::thread::JoinHandle<Result<bool>>> = json_base
14        .members()
15        .map(|url| -> JoinHandle<Result<bool>> {
16            download_file_async(url.to_string(), basedir, true)
17        })
18        .collect();
19    // Wait for all the threads to funish
20    for jh in vresult {
21        jh.join().unwrap()?;
22    }
23
24    Ok(())
25}
26
27/// Download a list of files from a JSON file
28fn download_from_json(
29    v: &JsonValue,
30    basedir: std::path::PathBuf,
31    baseurl: String,
32    overwrite: &bool,
33    thandles: &mut Vec<JoinHandle<Result<bool>>>,
34) -> Result<()> {
35    if v.is_object() {
36        let r1: Vec<Result<()>> = v
37            .entries()
38            .map(|entry: (&str, &JsonValue)| -> Result<()> {
39                let pbnew = basedir.join(entry.0);
40                if !pbnew.is_dir() {
41                    std::fs::create_dir_all(pbnew.clone())?;
42                }
43                let mut newurl = baseurl.clone();
44                newurl.push_str(format!("/{}", entry.0).as_str());
45                download_from_json(entry.1, pbnew, newurl, overwrite, thandles)?;
46                Ok(())
47            })
48            .filter(|res| res.is_err())
49            .collect();
50        if !r1.is_empty() {
51            bail!("Could not parse entries");
52        }
53    } else if v.is_array() {
54        let r2: Vec<Result<()>> = v
55            .members()
56            .map(|val| -> Result<()> {
57                download_from_json(val, basedir.clone(), baseurl.clone(), overwrite, thandles)?;
58                Ok(())
59            })
60            .filter(|res| res.is_err())
61            .collect();
62        if !r2.is_empty() {
63            bail!("could not parse array entries");
64        }
65    } else if v.is_string() {
66        let mut newurl = baseurl;
67        newurl.push_str(format!("/{}", v).as_str());
68        thandles.push(download_file_async(newurl, &basedir, *overwrite));
69    } else {
70        bail!("invalid json for downloading files??!!");
71    }
72
73    Ok(())
74}
75
76fn download_datadir(basedir: PathBuf, baseurl: String, overwrite: &bool) -> Result<()> {
77    if !basedir.is_dir() {
78        std::fs::create_dir_all(basedir.clone())?;
79    }
80
81    let mut fileurl = baseurl.clone();
82    fileurl.push_str("/files.json");
83
84    let json_base: JsonValue = json::parse(download_to_string(fileurl.as_str())?.as_str())?;
85    let mut thandles: Vec<JoinHandle<Result<bool>>> = Vec::new();
86    download_from_json(&json_base, basedir, baseurl, overwrite, &mut thandles)?;
87    // Wait for all the threads to funish
88    for jh in thandles {
89        jh.join().unwrap()?;
90    }
91    Ok(())
92}
93
94///
95/// Download and update any necessary data files for "satkit" calculations
96///
97/// # Arguments
98/// dir: The directory to download to, optional.  If not provided, the default data directory is used.
99/// overwrite_if_exists: If true, overwrite any existing files.  If false, skip files that already exist.
100///
101/// # Returns
102/// Result<()>
103///
104/// # Notes
105///
106/// This function downloads the data files necessary for "satkit" calculations.  These files include
107/// data necessary for calculating the JPL ephemerides, inertial-to-Earth-fixed rotations, high-order
108/// gravity field coefficients, and other data necessary for satellite calculations.
109///
110/// The data files also include space weather and Earth orientation parameters.  These files are always
111/// downloaded, as they are updated at least daily.
112///
113pub fn update_datafiles(dir: Option<PathBuf>, overwrite_if_exists: bool) -> Result<()> {
114    let downloaddir = match dir {
115        Some(d) => d,
116        None => datadir()?,
117    };
118    if downloaddir.metadata()?.permissions().readonly() {
119        bail!(
120            r#"
121            Data directory is read-only.
122            Try setting SATKIT_DATA environment
123            variable to a writeable directory and re-starting
124            "#
125        );
126    }
127
128    println!(
129        "Downloading data files to {}",
130        downloaddir.to_str().unwrap()
131    );
132    // Download old files
133    download_datadir(
134        downloaddir.clone(),
135        String::from("https://storage.googleapis.com/astrokit-astro-data"),
136        &overwrite_if_exists,
137    )?;
138
139    println!("Now downloading files that are regularly updated:");
140    println!("  Space Weather & Earth Orientation Parameters");
141    // Get a list of files that are updated with new data, and download them
142    download_from_url_json(
143        String::from("https://storage.googleapis.com/astrokit-astro-data/files_refresh.json"),
144        &downloaddir,
145    )?;
146    Ok(())
147}