148 lines
4.7 KiB
Rust
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),
|
|
}
|