lockman/src/download_command.rs
2024-08-31 16:28:01 +02:00

148 lines
4.7 KiB
Rust

use std::{fs::File, io::Write, path::PathBuf, process::exit, time::SystemTime};
use reqwest::StatusCode;
use sha2::{Digest, Sha512};
use crate::{
check_command::{check_item, CheckResult},
cli::{colors::{GREEN, RED, RESET}, DownloadArgs, DownloadExistingTreatment},
lockfile::{load_project_printing, LockedFile3},
};
pub fn download_command(args: DownloadArgs) {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()
.expect("Failed to build tokio runtime");
rt.block_on(download_command_async(args));
}
async fn download_command_async(args: DownloadArgs) {
let res = load_project_printing();
for record in res.locked {
handle_locked_file(record, args).await;
}
}
async fn handle_locked_file(record: LockedFile3, args: DownloadArgs) {
let check_res = check_item(&record);
match check_res {
CheckResult::Ok(_) => {
println!("{GREEN}OK{RESET}:\t {}", &record.path);
}
CheckResult::Invalid(invalid_hash) => match args.existing_file_behavior {
DownloadExistingTreatment::ValidateReplace => {
println!(
"Downloading and replacing invalid file:\n\t{}",
&record.path
);
let _ = download_file(args, &record).await.unwrap();
println!("Replaced invalid file:\n\t{}", &record.path);
}
DownloadExistingTreatment::ValidateFail => {
println!("Existing file has an invalid hash:\n\t{}\n\texpected: {}\n\tfound: {invalid_hash} ;)", record.path, record.expected_hash);
}
DownloadExistingTreatment::ValidateReport => todo!(),
DownloadExistingTreatment::Ignore => todo!(),
},
CheckResult::NotAFile => {
println!("{RED}ERROR{RESET}:\tPath {} exists but is a directory.\nThis is unsupported at the moment", record.path);
}
CheckResult::Absent => {
println!("Downloading absent file:\n\t{}", &record.path);
match download_file(args, &record).await {
Ok(_) => {
println!("Downloaded absent file:\n\t{}", &record.path);
}
Err(download_error) => handle_download_error(download_error, &record),
}
}
}
}
fn handle_download_error(download_error: DownloadError, record: &LockedFile3) {
match download_error {
DownloadError::ErrorResponse(sc) => {
println!(
"Received an error HTTP status code ({sc}) upon downloading file: \n\t{}",
record.path
);
exit(5);
}
DownloadError::HashMismatch {
calculated,
expected,
} => {
println!(
"Downloaded file has a different hash:\n\tfile:{} \n\texpected: {}\n\treceived: {}",
record.path, expected, calculated
);
exit(5);
}
DownloadError::IoError(err) => {
println!(
"An I/O error occurred while attempting to download a file: {}\n{err}",
record.path
);
}
}
}
async fn download_file(args: DownloadArgs, record: &LockedFile3) -> Result<String, DownloadError> {
let mut res = reqwest::get(&record.url).await.unwrap();
if res.status() != StatusCode::OK {
return Err(DownloadError::ErrorResponse(res.status()));
}
let mut hasher = Sha512::new();
let mut file = if args.direct_write {
// TODO: Handle not-existent paths
File::create(&record.path).unwrap()
} else {
todo!()
};
while let Some(chunk) = res.chunk().await.unwrap() {
hasher.write_all(&chunk).unwrap();
file.write_all(&chunk).unwrap();
}
let hash = hasher.finalize();
let hash = format!("{hash:x}");
if hash != record.expected_hash {
return Err(DownloadError::HashMismatch {
calculated: hash,
expected: record.expected_hash.clone(),
});
}
if let Err(err) = file.flush() {
return Err(DownloadError::IoError(err));
}
Ok(hash)
}
/// TODO: not in use yet
fn get_temp_dir() -> PathBuf {
let particle = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
let particle = format!("{}-{particle}", env!("CARGO_BIN_NAME"));
let mut path = std::env::temp_dir();
path.push(particle);
path
}
#[derive(Debug)]
enum DownloadError {
ErrorResponse(StatusCode),
HashMismatch {
calculated: String,
expected: String,
},
IoError(std::io::Error),
}