mirror of
https://github.com/flibusta-apps/books_downloader.git
synced 2025-12-06 06:55:37 +01:00
Add rust implementation
This commit is contained in:
202
src/services/downloader/mod.rs
Normal file
202
src/services/downloader/mod.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
pub mod zip;
|
||||
|
||||
use reqwest::Response;
|
||||
|
||||
use crate::config;
|
||||
|
||||
use self::types::{DownloadResult, Data, SpooledTempAsyncRead};
|
||||
use self::utils::response_to_tempfile;
|
||||
use self::zip::{unzip, zip};
|
||||
|
||||
use super::book_library::types::Book;
|
||||
use super::covert::convert_file;
|
||||
use super::{book_library::get_remote_book, filename_getter::get_filename_by_book};
|
||||
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
|
||||
pub async fn download<'a>(
|
||||
book_id: &'a u32,
|
||||
book_file_type: &'a str,
|
||||
source_config: &'a config::SourceConfig,
|
||||
) -> Option<(Response, bool)> {
|
||||
let basic_url = &source_config.url;
|
||||
let proxy = &source_config.proxy;
|
||||
|
||||
let url = if book_file_type == "fb2" || book_file_type == "epub" || book_file_type == "mobi" {
|
||||
format!("{basic_url}/b/{book_id}/{book_file_type}")
|
||||
} else {
|
||||
format!("{basic_url}/b/{book_id}/download")
|
||||
};
|
||||
|
||||
let client = match proxy {
|
||||
Some(v) => {
|
||||
let proxy_data = reqwest::Proxy::http(v);
|
||||
reqwest::Client::builder()
|
||||
.proxy(proxy_data.unwrap())
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
None => reqwest::Client::new(),
|
||||
};
|
||||
|
||||
let response = client.get(url).send().await;
|
||||
|
||||
let response = match response {
|
||||
Ok(v) => v,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let response = match response.error_for_status() {
|
||||
Ok(v) => v,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let headers = response.headers();
|
||||
let content_type = match headers.get("Content-Type") {
|
||||
Some(v) => v.to_str().unwrap(),
|
||||
None => "",
|
||||
};
|
||||
|
||||
if book_file_type.to_lowercase() == "html" && content_type.contains("text/html") {
|
||||
return Some((response, false));
|
||||
}
|
||||
|
||||
if content_type.contains("text/html")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let is_zip = content_type.contains("application/zip");
|
||||
|
||||
Some((response, is_zip))
|
||||
}
|
||||
|
||||
pub async fn download_chain<'a>(
|
||||
book: &'a Book,
|
||||
file_type: &'a str,
|
||||
source_config: &'a config::SourceConfig,
|
||||
converting: bool
|
||||
) -> Option<DownloadResult> {
|
||||
let final_need_zip = file_type == "fb2zip";
|
||||
|
||||
let file_type_ = if converting {
|
||||
&book.file_type
|
||||
} else {
|
||||
file_type
|
||||
};
|
||||
|
||||
let (mut response, is_zip) = match download(&book.remote_id, file_type_, source_config).await {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
if is_zip && book.file_type.to_lowercase() == "html" {
|
||||
let filename = get_filename_by_book(book, file_type, true);
|
||||
return Some(DownloadResult::new(Data::Response(response), filename));
|
||||
}
|
||||
|
||||
if !is_zip && !final_need_zip && !converting {
|
||||
let filename = get_filename_by_book(book, &book.file_type, false);
|
||||
return Some(DownloadResult::new(Data::Response(response), filename));
|
||||
};
|
||||
|
||||
let unziped_temp_file = {
|
||||
let temp_file_to_unzip_result = response_to_tempfile(&mut response).await;
|
||||
let temp_file_to_unzip = match temp_file_to_unzip_result {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
match unzip(temp_file_to_unzip, "fb2") {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let mut clean_file = if converting {
|
||||
match convert_file(unziped_temp_file, file_type.to_string()).await {
|
||||
Some(mut response) => {
|
||||
match response_to_tempfile(&mut response).await {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
}
|
||||
},
|
||||
None => return None,
|
||||
}
|
||||
} else {
|
||||
unziped_temp_file
|
||||
};
|
||||
|
||||
if !final_need_zip {
|
||||
let t = SpooledTempAsyncRead::new(clean_file);
|
||||
let filename = get_filename_by_book(book, file_type, false);
|
||||
return Some(DownloadResult::new(Data::SpooledTempAsyncRead(t), filename));
|
||||
};
|
||||
|
||||
let t_file_type = if file_type == "fb2zip" { "fb2" } else { file_type };
|
||||
let filename = get_filename_by_book(book, t_file_type, false);
|
||||
match zip(&mut clean_file, filename.as_str()) {
|
||||
Some(v) => {
|
||||
let t = SpooledTempAsyncRead::new(v);
|
||||
let filename = get_filename_by_book(book, file_type, true);
|
||||
Some(DownloadResult::new(Data::SpooledTempAsyncRead(t), filename))
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_download_futures(
|
||||
book: &Book,
|
||||
file_type: &str,
|
||||
) -> Option<DownloadResult> {
|
||||
let mut futures = FuturesUnordered::new();
|
||||
|
||||
for source_config in &config::CONFIG.fl_sources {
|
||||
futures.push(download_chain(
|
||||
book,
|
||||
file_type,
|
||||
source_config,
|
||||
false
|
||||
));
|
||||
|
||||
if file_type == "epub" || file_type == "fb2" {
|
||||
futures.push(download_chain(
|
||||
book,
|
||||
file_type.clone(),
|
||||
source_config,
|
||||
true
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(result) = futures.next().await {
|
||||
match result {
|
||||
Some(v) => return Some(v),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn book_download(
|
||||
source_id: u32,
|
||||
remote_id: u32,
|
||||
file_type: &str,
|
||||
) -> Result<Option<(DownloadResult, String)>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let book = match get_remote_book(source_id, remote_id).await {
|
||||
Ok(v) => v,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
let filename = get_filename_by_book(&book, file_type, false);
|
||||
|
||||
match start_download_futures(&book, file_type).await {
|
||||
Some(v) => Ok(Some((v, filename))),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user