mangadexrs/src/commands/download.rs

195 lines
8.8 KiB
Rust

use crate::utils;
use crate::structs;
use std::sync::Arc;
use std::fs::create_dir_all;
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::process::exit;
use clap::ArgMatches;
use tokio::sync::{Mutex, RwLock};
use tokio::task::JoinHandle;
use tokio::time::{sleep, Duration};
extern crate tokio;
extern crate reqwest;
const DOWNLOAD_WORKERS: usize = 5;
const NON_IMAGE_WAIT_TIME: Duration = Duration::from_millis(5000);
const NO_ITEM_WAIT_TIME: Duration = Duration::from_millis(1000);
const GET_DATA_FAIL_WAIT_TIME: Duration = Duration::from_millis(30000);
pub async fn download(arg_m: &ArgMatches<'_>) {
let print_only = arg_m.is_present("print");
let languages: Vec<_> = arg_m.values_of("language").unwrap_or_default().collect();
let mut chapter_ids: Vec<_> = arg_m.values_of("chapter_ids").unwrap_or_default().map(|i| i.parse::<i32>().unwrap()).collect();
chapter_ids.sort();
chapter_ids.dedup();
let mut manga_ids: Vec<_> = arg_m.values_of("manga_ids").unwrap_or_default().map(|i| i.parse::<i32>().unwrap()).collect();
manga_ids.sort();
manga_ids.dedup();
let client = reqwest::Client::new();
let mut return_fail = false;
let mutex = Arc::new(Mutex::new(DownloadData { data: VecDeque::new(), is_done: false }));
let handles = summon_handles(client.clone(), Arc::clone(&mutex)).await;
handle_chapters(client.clone(), chapter_ids.clone(), Arc::clone(&mutex), &mut return_fail, print_only).await;
for manga_id in manga_ids {
let cloned_client = client.clone();
let cloned_mutex = Arc::clone(&mutex);
loop {
let mut manga = match utils::get_manga(cloned_client.clone(), manga_id).await {
Ok(Some(i)) => i,
Ok(None) => {
eprintln!("Manga ID: {}\nError: does not exist", manga_id);
return_fail = true;
break;
},
Err(err) => {
eprintln!("Manga ID: {}\nError: {}", manga_id, err);
sleep(GET_DATA_FAIL_WAIT_TIME).await;
continue;
}
};
let mut mchapter_ids = Vec::new();
manga.data.chapters.reverse();
for chapter in manga.data.chapters {
if chapter_ids.contains(&chapter.id) || (!languages.is_empty() && !languages.contains(&chapter.language.as_str())) {
continue;
}
mchapter_ids.push(chapter.id);
}
if !mchapter_ids.is_empty() {
handle_chapters(cloned_client.clone(), mchapter_ids, Arc::clone(&cloned_mutex), &mut return_fail, print_only).await;
}
break;
}
}
mutex.lock().await.is_done = true;
for handle in handles {
handle.await.unwrap();
}
if return_fail {
exit(1);
}
}
async fn handle_chapters(client: reqwest::Client, chapter_ids: Vec<i32>, mutex: Arc<Mutex<DownloadData>>, return_fail: &mut bool, print_only: bool) {
let mut chapter_datas = Vec::new();
for chapter_id in chapter_ids {
let cloned_client = client.clone();
loop {
let chapter = match utils::get_chapter(cloned_client.clone(), chapter_id).await {
Ok(Some(i)) => i,
Ok(None) => {
eprintln!("Chapter ID: {}\nError: does not exist", chapter_id);
*return_fail = true;
break;
},
Err(err) => {
eprintln!("Chapter ID: {}\nError: {}", chapter_id, err);
sleep(GET_DATA_FAIL_WAIT_TIME).await;
continue;
}
};
if print_only {
println!("{}", chapter.data.pages.iter().map(|i| format!("{}{}/{}", &chapter.data.server, &chapter.data.hash, i)).collect::<Vec<_>>().join(";"));
} else {
let manga_slug = utils::generate_slug(&chapter.data.manga_title);
let local_dir: PathBuf = [&manga_slug, &chapter.data.chapter].iter().collect();
let chapter_pages = chapter.data.pages.clone();
let chapter_rwlock = Arc::new(RwLock::new((chapter.data, 0)));
for (i, server_file) in chapter_pages.iter().enumerate() {
let mut local_file = local_dir.clone();
local_file.push((i + 1).to_string());
if let Some(ext) = Path::new(&server_file).extension() {
local_file.set_extension(ext);
}
if local_file.exists() {
continue;
}
chapter_datas.push((Arc::clone(&chapter_rwlock), i, local_file));
}
}
break;
}
}
if !chapter_datas.is_empty() {
mutex.lock().await.data.extend(chapter_datas);
}
}
async fn summon_handles(client: reqwest::Client, mutex: Arc<Mutex<DownloadData>>) -> Vec<JoinHandle<()>> {
let mut handles = Vec::with_capacity(DOWNLOAD_WORKERS);
for worker_id in 0..DOWNLOAD_WORKERS {
let tcloned_mutex = Arc::clone(&mutex);
let tcloned_client = client.clone();
handles.push(tokio::spawn(async move {
eprintln!("[DW{}] Up!", worker_id);
loop {
let cloned_mutex = Arc::clone(&tcloned_mutex);
let cloned_client = tcloned_client.clone();
let mut download_data = cloned_mutex.lock().await;
let data = download_data.data.pop_front();
let is_done = download_data.is_done;
drop(download_data);
let (chapter_rwlock, page_index, local_file) = match data {
Some(data) => data,
None => {
if is_done {
break;
}
sleep(NO_ITEM_WAIT_TIME).await;
continue;
}
};
let read_guard = chapter_rwlock.read().await;
let chapter_id = read_guard.0.id;
let mut server = read_guard.0.server.clone();
let mut server_fallback = read_guard.0.server_fallback.clone();
let mut hash = read_guard.0.hash.clone();
let mut server_file = read_guard.0.pages[page_index].clone();
let mut local_update_count = read_guard.1;
drop(read_guard);
if let Some(parent) = local_file.parent() {
match create_dir_all(&parent) {
Ok(()) => (),
Err(err) => eprintln!("[DW{}] Error while creating directories {}: {}", worker_id, parent.display(), err)
};
}
eprintln!("[DW{}] Downloading {}", worker_id, local_file.display());
loop {
match utils::download_page(cloned_client.clone(), &server, &server_fallback, &hash, &server_file, &local_file).await {
Ok(true) => break,
Ok(false) => eprintln!("[DW{}] Error while downloading {}: does not exist", worker_id, local_file.display()),
Err(err) => eprintln!("[DW{}] Error while downloading {}: {}", worker_id, local_file.display(), err)
};
sleep(NON_IMAGE_WAIT_TIME).await;
let mut write_guard = chapter_rwlock.write().await;
if local_update_count >= write_guard.1 {
match utils::get_chapter(cloned_client.clone(), chapter_id).await {
Ok(Some(chapter_data)) => {
write_guard.0 = chapter_data.data;
write_guard.1 = local_update_count + 1;
},
Ok(None) => eprintln!("[DW{}] Error while fetching chapter data {}: does not exist", worker_id, chapter_id),
Err(err) => eprintln!("[DW{}] Error while fetching chapter data {}: {}", worker_id, chapter_id, err)
};
}
server = write_guard.0.server.clone();
server_fallback = write_guard.0.server_fallback.clone();
hash = write_guard.0.hash.clone();
server_file = write_guard.0.pages[page_index].clone();
local_update_count = write_guard.1;
}
eprintln!("[DW{}] Downloaded {}", worker_id, local_file.display());
}
eprintln!("[DW{}] Down!", worker_id);
}));
}
handles
}
struct DownloadData {
pub data: VecDeque<(Arc<RwLock<(structs::ChapterData, i32)>>, usize, PathBuf)>,
pub is_done: bool
}