2023-12-02 20:47:26 +01:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2021-12-21 11:06:51 +01:00
|
|
|
use std::fs;
|
2024-03-26 01:04:21 +01:00
|
|
|
use std::io::{Read};
|
|
|
|
|
use std::path::{PathBuf};
|
2024-03-26 16:07:31 +01:00
|
|
|
use log::{debug, error, Level, log_enabled};
|
2023-11-06 21:33:48 +01:00
|
|
|
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
2023-10-13 13:59:22 +02:00
|
|
|
use crate::create_m3u_filter_error_result;
|
|
|
|
|
use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind};
|
|
|
|
|
use crate::model::config::{ConfigInput};
|
2024-03-26 01:04:21 +01:00
|
|
|
use crate::utils::file_utils::{get_file_path, open_file, persist_file};
|
2021-09-29 20:48:32 +02:00
|
|
|
|
2024-03-26 01:04:21 +01:00
|
|
|
pub(crate) fn bytes_to_megabytes(bytes: u64) -> u64 {
|
|
|
|
|
bytes / 1_048_576
|
2021-09-29 20:48:32 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-03 18:05:13 +01:00
|
|
|
pub(crate) async fn get_input_text_content(input: &ConfigInput, working_dir: &String, url_str: &str, persist_filepath: Option<PathBuf>) -> Result<String, M3uFilterError> {
|
2024-03-26 16:07:31 +01:00
|
|
|
if log_enabled!(Level::Debug) {
|
|
|
|
|
debug!("getting input text content working_dir: {}, url: {}", working_dir, url_str);
|
|
|
|
|
}
|
2024-05-10 12:01:48 +02:00
|
|
|
|
|
|
|
|
if let Ok(url) = url_str.parse::<url::Url>() {
|
|
|
|
|
match download_text_content(input, url, persist_filepath).await {
|
2023-10-13 13:59:22 +02:00
|
|
|
Ok(content) => Ok(content),
|
2021-09-29 20:48:32 +02:00
|
|
|
Err(e) => {
|
2023-10-08 19:54:59 +02:00
|
|
|
error!("cant download input url: {} => {}", url_str, e);
|
2023-10-13 13:59:22 +02:00
|
|
|
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to download")
|
2021-09-29 20:48:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-05-10 12:01:48 +02:00
|
|
|
} else {
|
|
|
|
|
let result = match get_file_path(working_dir, Some(PathBuf::from(url_str))) {
|
|
|
|
|
Some(filepath) => {
|
|
|
|
|
if filepath.exists() {
|
|
|
|
|
if let Some(persist_file_value) = persist_filepath {
|
|
|
|
|
let to_file = &persist_file_value;
|
|
|
|
|
match fs::copy(&filepath, to_file) {
|
|
|
|
|
Ok(_) => {}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
error!("cant persist to: {} => {}", to_file.to_str().unwrap_or("?"), e);
|
|
|
|
|
return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Failed to persist: {} => {}", to_file.to_str().unwrap_or("?"), e);
|
2021-10-15 16:59:20 +02:00
|
|
|
}
|
2024-05-10 12:01:48 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
match open_file(&filepath) {
|
|
|
|
|
Ok(file) => {
|
|
|
|
|
let mut content = String::new();
|
|
|
|
|
match std::io::BufReader::new(file).read_to_string(&mut content) {
|
|
|
|
|
Ok(_) => Some(content),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
let file_str = &filepath.to_str().unwrap_or("?");
|
|
|
|
|
error!("cant read file: {} {}", file_str, err);
|
|
|
|
|
return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Cant open file : {} => {}", file_str, err);
|
2023-10-27 19:03:29 +02:00
|
|
|
}
|
2023-11-24 22:53:33 +01:00
|
|
|
}
|
2023-10-12 19:17:25 +02:00
|
|
|
}
|
2024-05-10 12:01:48 +02:00
|
|
|
Err(err) => {
|
|
|
|
|
let file_str = &filepath.to_str().unwrap_or("?");
|
|
|
|
|
error!("cant read file: {} {}", file_str, err);
|
|
|
|
|
return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "Cant open file : {} => {}", file_str, err);
|
|
|
|
|
}
|
2021-09-29 20:48:32 +02:00
|
|
|
}
|
2024-05-10 12:01:48 +02:00
|
|
|
} else {
|
|
|
|
|
None
|
2021-10-15 16:59:20 +02:00
|
|
|
}
|
2021-09-29 20:48:32 +02:00
|
|
|
}
|
2024-05-10 12:01:48 +02:00
|
|
|
None => None
|
|
|
|
|
};
|
|
|
|
|
if let Some(content) = result {
|
|
|
|
|
Ok(content)
|
|
|
|
|
} else {
|
|
|
|
|
let msg = format!("cant read input url: {url_str:?}");
|
|
|
|
|
error!("{}", msg);
|
|
|
|
|
create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "{}", msg)
|
2021-09-29 20:48:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-01 18:02:02 +02:00
|
|
|
pub(crate) fn get_client_request(input: Option<&ConfigInput>, url: url::Url, custom_headers: Option<&HashMap<&str, &[u8]>>) -> reqwest::RequestBuilder {
|
2023-11-03 18:05:13 +01:00
|
|
|
let mut request = reqwest::Client::new().get(url);
|
2024-04-01 18:02:02 +02:00
|
|
|
let headers = get_request_headers(input.map_or(&HashMap::new(), |i| &i.headers), custom_headers);
|
2023-12-05 17:54:42 +01:00
|
|
|
request = request.headers(headers);
|
2023-10-27 19:03:29 +02:00
|
|
|
request
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-01 21:38:23 +01:00
|
|
|
pub(crate) fn get_request_headers(defined_headers: &HashMap<String, String>, custom_headers: Option<&HashMap<&str, &[u8]>>) -> HeaderMap {
|
|
|
|
|
let mut headers = HeaderMap::new();
|
2023-11-06 21:33:48 +01:00
|
|
|
for (key, value) in defined_headers {
|
|
|
|
|
headers.insert(
|
|
|
|
|
HeaderName::from_bytes(key.as_bytes()).unwrap(),
|
2023-12-01 21:38:23 +01:00
|
|
|
HeaderValue::from_bytes(value.as_bytes()).unwrap());
|
|
|
|
|
}
|
|
|
|
|
if let Some(custom) = custom_headers {
|
2023-12-02 20:47:26 +01:00
|
|
|
let header_keys: HashSet<String> = headers.keys().map(|k| k.as_str().to_lowercase()).collect();
|
2023-12-01 21:38:23 +01:00
|
|
|
for (key, value) in custom {
|
2023-12-02 20:47:26 +01:00
|
|
|
let key_lc = key.to_lowercase();
|
|
|
|
|
if !("host" == key_lc || header_keys.contains(key_lc.as_str())) {
|
2023-12-01 21:38:23 +01:00
|
|
|
headers.insert(
|
|
|
|
|
HeaderName::from_bytes(key.as_bytes()).unwrap(),
|
|
|
|
|
HeaderValue::from_bytes(value).unwrap());
|
2024-03-26 16:07:31 +01:00
|
|
|
} else if log_enabled!(Level::Debug) {
|
2023-12-05 17:54:42 +01:00
|
|
|
debug!("Ignoring request header {}={}", key_lc, String::from_utf8_lossy(value));
|
2023-12-01 21:38:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
2023-11-06 21:33:48 +01:00
|
|
|
}
|
2024-03-26 16:07:31 +01:00
|
|
|
if log_enabled!(Level::Debug) {
|
2023-12-05 17:54:42 +01:00
|
|
|
let he: HashMap<String, String> = headers.iter().map(|(k, v)| (k.to_string(), String::from_utf8_lossy(v.as_bytes()).to_string())).collect();
|
|
|
|
|
debug!("Request headers {:?}", he);
|
|
|
|
|
}
|
2023-11-06 21:33:48 +01:00
|
|
|
headers
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-03 18:05:13 +01:00
|
|
|
async fn download_json_content(input: &ConfigInput, url: url::Url, persist_filepath: Option<PathBuf>) -> Result<serde_json::Value, String> {
|
2024-03-26 16:07:31 +01:00
|
|
|
if log_enabled!(Level::Debug) {
|
|
|
|
|
debug!("downloading json content from {}", url.to_string());
|
|
|
|
|
}
|
2024-04-01 18:02:02 +02:00
|
|
|
let request = get_client_request(Some(input), url, None);
|
2023-11-03 18:05:13 +01:00
|
|
|
match request.send().await {
|
2023-03-19 17:16:05 +01:00
|
|
|
Ok(response) => {
|
2024-03-26 16:07:31 +01:00
|
|
|
if log_enabled!(Level::Debug) {
|
|
|
|
|
debug!("downloading json content response code: {}", response.status().as_str());
|
|
|
|
|
}
|
2023-03-19 17:16:05 +01:00
|
|
|
if response.status().is_success() {
|
2023-11-03 18:05:13 +01:00
|
|
|
match response.json::<serde_json::Value>().await {
|
2023-03-19 17:16:05 +01:00
|
|
|
Ok(content) => {
|
2023-10-27 19:03:29 +02:00
|
|
|
if persist_filepath.is_some() {
|
|
|
|
|
persist_file(persist_filepath, &serde_json::to_string(&content).unwrap());
|
2023-10-06 19:24:06 +02:00
|
|
|
}
|
2023-03-19 17:16:05 +01:00
|
|
|
Ok(content)
|
|
|
|
|
}
|
|
|
|
|
Err(e) => Err(e.to_string())
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Err(format!("Request failed: {}", response.status()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(e) => Err(e.to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-08 19:08:04 +01:00
|
|
|
pub(crate) async fn get_input_json_content(input: &ConfigInput, url_str: &str, persist_filepath: Option<PathBuf>) -> Result<serde_json::Value, M3uFilterError> {
|
2023-03-19 17:16:05 +01:00
|
|
|
match url_str.parse::<url::Url>() {
|
2023-11-03 18:05:13 +01:00
|
|
|
Ok(url) => match download_json_content(input, url, persist_filepath).await {
|
2023-10-13 13:59:22 +02:00
|
|
|
Ok(content) => Ok(content),
|
|
|
|
|
Err(e) => create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "cant download input url: {} => {}", url_str, e)
|
2023-03-19 17:16:05 +01:00
|
|
|
},
|
2023-10-13 13:59:22 +02:00
|
|
|
Err(_) => create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "malformed input url: {}", url_str)
|
2023-03-19 17:16:05 +01:00
|
|
|
}
|
2023-10-20 12:18:44 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-03 18:05:13 +01:00
|
|
|
async fn download_text_content(input: &ConfigInput, url: url::Url, persist_filepath: Option<PathBuf>) -> Result<String, String> {
|
2024-04-01 18:02:02 +02:00
|
|
|
let request = get_client_request(Some(input), url, None);
|
2023-11-03 18:05:13 +01:00
|
|
|
match request.send().await {
|
2023-10-27 19:03:29 +02:00
|
|
|
Ok(response) => {
|
|
|
|
|
if response.status().is_success() {
|
2023-11-03 18:05:13 +01:00
|
|
|
match response.text_with_charset("utf8").await {
|
2023-10-27 19:03:29 +02:00
|
|
|
Ok(content) => {
|
|
|
|
|
if persist_filepath.is_some() {
|
|
|
|
|
persist_file(persist_filepath, &content);
|
|
|
|
|
}
|
|
|
|
|
Ok(content)
|
|
|
|
|
}
|
|
|
|
|
Err(e) => Err(e.to_string())
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Err(format!("Request failed: {}", response.status()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(e) => Err(e.to_string())
|
|
|
|
|
}
|
2024-03-26 01:04:21 +01:00
|
|
|
}
|