use crate::cloud_image::CloudImage;
use crate::image_history::DbImageHistory;
use crate::website::WSImageList;
use crate::{CID_USER_AGENT, CONCURRENT_REQUESTS};
use clap_verbosity_flag::Verbosity;
use colored::Colorize;
use log::{error, info, warn};
use reqwest::Url;
use reqwest::header::{HeaderValue, USER_AGENT};
use std::error::Error;
use std::fs::create_dir_all;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::task;
use trauma::download::Status;
use trauma::{
download::{Download, Summary},
downloader::{Downloader, DownloaderBuilder},
};
fn create_dir_if_needed(path: &PathBuf) -> Result<(), Box<dyn Error>> {
if !path.exists() {
create_dir_all(path)?;
}
Ok(())
}
#[must_use]
pub fn get_filename_destination(
cloud_image: &CloudImage,
file_destination: &Path,
normalize: &Option<String>,
) -> Option<String> {
let image_date = cloud_image.date;
let image_name = &cloud_image.name;
let version = match &cloud_image.url.version {
Some(v) => v,
None => "",
};
let after_version = match &cloud_image.url.after_version {
Some(av) => av,
None => "",
};
let normalized_image_name: String;
if normalize.is_some()
&& let Some(normalize_string) = normalize
{
normalized_image_name = normalize_string
.replace("{date}", &image_date.format("%Y%m%d").to_string())
.replace("{version}", version)
.replace("{after_version}", after_version);
} else {
normalized_image_name = image_name.clone();
}
if let Some(filename) = file_destination.join(&normalized_image_name).to_str() {
Some(filename.to_string())
} else {
warn!("{normalized_image_name} is not a valid UTF-8 string");
None
}
}
pub async fn download_images(
all_ws_image_lists: &Vec<WSImageList>,
verbose: &Verbosity,
concurrent_download: usize,
) -> Vec<Summary> {
let mut download_image_list = vec![];
for ws_image in all_ws_image_lists {
for cloud_image in &ws_image.images_list {
match create_dir_if_needed(&ws_image.website.destination) {
Ok(()) => {
if let Some(filename) = get_filename_destination(
cloud_image,
&ws_image.website.destination,
&ws_image.website.normalize,
) {
match Url::parse(&cloud_image.url.url) {
Ok(url) => {
info!("Will try to download '{}' to {filename}", cloud_image.url.url);
download_image_list.push(Download::new(&url, &filename));
}
Err(e) => {
error!("Can not transform '{}' into reqwest Url type: {e}", cloud_image.url.url);
info!("As a result {filename} will not be downloaded");
}
}
}
}
Err(e) => {
warn!(
"Error '{e}' while creating destination {} directory",
&ws_image.website.destination.display()
);
}
}
}
}
let downloader: Downloader;
let retry = 3;
let user_agent =
HeaderValue::from_str(CID_USER_AGENT).expect("Converting CID_USER_AGENT to HeaderValue should not fail");
let max: usize = if concurrent_download > 0 {
concurrent_download
} else {
CONCURRENT_REQUESTS
};
if verbose.is_silent() {
downloader =
DownloaderBuilder::hidden().concurrent_downloads(max).header(USER_AGENT, user_agent).retries(retry).build();
} else {
downloader =
DownloaderBuilder::new().concurrent_downloads(max).header(USER_AGENT, user_agent).retries(retry).build();
}
downloader.download(&download_image_list).await
}
pub fn display_download_status_summary(downloaded_summary: &Vec<Summary>, verbose: &Verbosity) {
if !verbose.is_silent() {
for summary in downloaded_summary {
let download = summary.download();
match summary.status() {
Status::Success => {
println!("{} Successfully downloaded {}", "🗸".green(), download.filename);
}
Status::Fail(e) => {
println!("{} Error '{e}' while downloading {} to {}", "𐄂".red(), download.url, download.filename);
}
Status::Skipped(e) => {
println!(
"{} Skipped {} to be downloaded from {}: '{e}' ",
"🗸".green(),
download.filename,
download.url
);
}
Status::NotStarted => {
println!(
"{} Downloading {} to {} has not been started",
"𐄂".red(),
download.url,
download.filename
);
}
}
}
}
}
#[must_use]
pub fn image_has_been_downloaded(
downloaded_summary: &Vec<Summary>,
cloud_image: &CloudImage,
destination: &Path,
verify_skipped: bool,
normalize: &Option<String>,
) -> bool {
for summary in downloaded_summary {
let download = summary.download();
if let Some(filename) = get_filename_destination(cloud_image, destination, normalize) {
match Url::parse(&cloud_image.url.url) {
Ok(url) => {
if download.filename == filename && download.url == url {
match summary.status() {
Status::Success => {
info!("Keeping image {filename} from {url}");
return true;
}
Status::Fail(_) | Status::NotStarted => {
return false;
}
Status::Skipped(_) => {
return verify_skipped;
}
}
}
}
Err(e) => {
error!("Can not transform '{}' into reqwest Url type: {e}", cloud_image.url.url);
}
}
}
}
false
}
pub async fn verify_downloaded_file(
all_ws_image_lists: Vec<WSImageList>,
db: Arc<DbImageHistory>,
downloaded_summary: &Vec<Summary>,
verify_skipped: bool,
) {
let mut join_handle_list = Vec::new();
for ws_image in all_ws_image_lists {
for cloud_image in ws_image.images_list {
if image_has_been_downloaded(
downloaded_summary,
&cloud_image,
&ws_image.website.destination,
verify_skipped,
&ws_image.website.normalize,
) {
let website = ws_image.website.clone();
let join_handle = task::spawn(async move {
if cloud_image.verify(&website.destination, &website.normalize) {
Some(cloud_image)
} else {
None
}
});
join_handle_list.push(join_handle);
}
}
}
for join_handle in join_handle_list {
match join_handle.await {
Ok(option_cloud_image) => {
if let Some(cloud_image) = option_cloud_image {
db.save_image_in_db(&cloud_image);
}
}
Err(e) => error!("Error in task: {e}"),
}
}
}