pub mod structs;
use std::fs::{create_dir_all, File};
use std::io;
use std::path::Path;
use std::time::Duration;
use color_eyre::eyre::Result;
use indicatif::ProgressBar;
use log::info;
use reqwest::StatusCode;
use sha2::digest::DynDigest;
use sha2::{Digest, Sha256};
use crate::error::ErrorKind;
use crate::metadata::structs::Metadata;
pub fn download_metadata(cache: &Path) -> Result<Metadata>
{
let metadata_file_name = cache.join("metadata.json");
let etag = compute_etag(metadata_file_name.as_path())?;
let progress = ProgressBar::new_spinner()
.with_message("Updating release metadata cache");
progress.enable_steady_tick(Duration::from_millis(100));
let client = reqwest::blocking::Client::new();
let mut request = client.get("https://summon.black-magic.org/metadata.json")
.timeout(Duration::from_secs(2));
if let Some(etag) = etag {
request = request.header("If-None-Match", etag);
}
let mut response = request.send()?;
if response.status() == StatusCode::OK {
create_dir_all(cache)?;
let mut metadata_file = File::create(metadata_file_name.as_path())?;
response.copy_to(&mut metadata_file)?;
} else if response.status() != StatusCode::NOT_MODIFIED {
progress.finish();
return Err(ErrorKind::ReleaseMetadataInvalid.error().into());
}
progress.finish();
let file = File::open(metadata_file_name)?;
let metadata: Metadata = serde_json::from_reader(file)?;
match metadata.version {
1 => handle_v1_metadata(metadata),
_ => Err(ErrorKind::ReleaseMetadataInvalid.error().into()),
}
}
fn handle_v1_metadata(metadata: Metadata) -> Result<Metadata>
{
info!("Validating v1 metadata with {} releases present", metadata.releases.len());
for (_, release) in &metadata.releases {
if release.includes_bmda && release.bmda.is_none() {
return Err(ErrorKind::ReleaseMetadataInvalid.error().into());
}
if !release.includes_bmda && release.bmda.is_some() {
return Err(ErrorKind::ReleaseMetadataInvalid.error().into());
}
}
Ok(metadata)
}
fn hex_digit(value: u8) -> char
{
let mut digit = value;
if value > 9 {
digit += 0x61 - 0x30 - 10;
}
digit += 0x30;
char::from(digit)
}
fn compute_etag(metadata_file_name: &Path) -> Result<Option<String>>
{
if !metadata_file_name.exists() {
return Ok(None);
}
let mut file = File::open(metadata_file_name)?;
let mut hasher = Sha256::default();
let hash_length = hasher.output_size();
io::copy(&mut file, &mut hasher)?;
let hash = hasher.finalize();
let mut result = String::with_capacity((hash_length * 2) + 2);
result.push('"');
for byte in hash {
result.push(hex_digit(byte >> 4));
result.push(hex_digit(byte & 0x0f));
}
result.push('"');
Ok(Some(result))
}