diff --git a/CHANGELOG.md b/CHANGELOG.md index ab324ca41..83d087659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * Added `panel_api.php` endpoint for xtream * Case insensitive filter syntax * Xtream category_id fixes, to avoid category_id change when title not changes. -* `xtream_skip_live_direct_source` and `xtream_skip_video_direct_source` are now default true +* Target options `xtream_skip_live_direct_source` and `xtream_skip_video_direct_source` are now default true * added new target option - `xtream_skip_series_direct_source` default is true * Added new options to input configuration. `xtream_skip_live`, `xtream_skip_vod`, `xtream_skip_series` @@ -22,7 +22,7 @@ - `secret` secret for jwt token - `userfile` optional userfile with generated userfile in format "username: password" per file, default name is user.txt in config path * Password generation argument --genpwd to generate passwords for userfile. -* Added env var ` M3U_FILTER_LOG` for log level +* Added env var `M3U_FILTER_LOG` for log level * Log Level has now module support like `m3u_filter::util=error,m3u_filter::filter=debug,m3u_filter=debug` * Multiple Xtream Sources merging into one target is now supported diff --git a/src/api/api_model.rs b/src/api/api_model.rs index 313f8ba55..742963e13 100644 --- a/src/api/api_model.rs +++ b/src/api/api_model.rs @@ -7,7 +7,8 @@ use serde::{Deserialize, Serialize}; use unidecode::unidecode; use crate::model::api_proxy::{ApiProxyConfig}; use crate::model::config::{Config, ConfigTargetOptions, ConfigRename, ConfigSort, InputType, ProcessTargets, TargetOutput, VideoConfig, VideoDownloadConfig, ConfigApi, MessagingConfig}; -use crate::model::config::{default_as_empty_str, ProcessingOrder}; +use crate::model::config::{ProcessingOrder}; +use crate::utils::default_utils::default_as_empty_str; /// File-Download information. #[derive(Clone)] @@ -41,18 +42,18 @@ pub(crate) struct FileDownload { /// fn get_download_directory(download_cfg: &VideoDownloadConfig, filestem: &str) -> PathBuf { if download_cfg.organize_into_directories { - let mut file_stem = filestem; + let mut stem = filestem; if let Some(re) = &download_cfg._re_episode_pattern { - if let Some(captures) = re.captures(file_stem) { + if let Some(captures) = re.captures(stem) { if let Some(episode) = captures.name("episode") { if !episode.as_str().is_empty() { - file_stem = &file_stem[..episode.start()]; + stem = &stem[..episode.start()]; } } } } let re_ending = download_cfg._re_remove_filename_ending.as_ref().unwrap(); - let dir_name = re_ending.replace(file_stem, ""); + let dir_name = re_ending.replace(stem, ""); let file_dir: PathBuf = [download_cfg.directory.as_ref().unwrap(), dir_name.as_ref()].iter().collect(); file_dir } else { diff --git a/src/api/api_utils.rs b/src/api/api_utils.rs index 16a44e945..803201140 100644 --- a/src/api/api_utils.rs +++ b/src/api/api_utils.rs @@ -67,9 +67,8 @@ pub(crate) async fn stream_response(stream_url: &str, req: &HttpRequest, input: response_builder.insert_header((k.as_str(), v.as_ref())); }); return response_builder.body(actix_web::body::BodyStream::new(response.bytes_stream())); - } else { - debug!("Failed to open stream got status {} for {}", response.status(), stream_url) } + debug!("Failed to open stream got status {} for {}", response.status(), stream_url) } Err(err) => { error!("Received failure from server {}: {}", stream_url, err) diff --git a/src/api/xmltv_api.rs b/src/api/xmltv_api.rs index b888c2fae..a3ac53328 100644 --- a/src/api/xmltv_api.rs +++ b/src/api/xmltv_api.rs @@ -18,7 +18,7 @@ fn get_epg_path_for_target_of_type(target_name: &str, file_path: Option if file_utils::path_exists(&epg_path) { return Some(epg_path); } else { - info!("Cant find epg file for {target_name} target: {}", epg_path.to_str().unwrap_or("?")) + info!("Cant find epg file for {target_name} target: {}", epg_path.to_str().unwrap_or("?")); } } None diff --git a/src/api/xtream_api.rs b/src/api/xtream_api.rs index 129bc1a77..a63eea881 100644 --- a/src/api/xtream_api.rs +++ b/src/api/xtream_api.rs @@ -23,7 +23,52 @@ use crate::repository::xtream_repository; use crate::repository::xtream_repository::{xtream_get_item_for_stream_id, xtream_get_max_series_info_episode_id, xtream_load_series_info, xtream_write_series_info}; use crate::utils::{json_utils, request_utils}; -pub(crate) async fn serve_query(file_path: &Path, filter: &HashMap<&str, &str>) -> HttpResponse { + +enum XtreamApiStreamContext { + LiveAlt, + Live, + Movie, + Series, + Timeshift, +} + +impl Display for XtreamApiStreamContext { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", match self { + XtreamApiStreamContext::LiveAlt => "", + XtreamApiStreamContext::Live => "live", + XtreamApiStreamContext::Movie => "movie", + XtreamApiStreamContext::Series => "series", + XtreamApiStreamContext::Timeshift => "timeshift", + }) + } +} + +struct XtreamApiStreamRequest<'a> { + context: XtreamApiStreamContext, + username: &'a str, + password: &'a str, + stream_id: &'a str, + action_path: &'a str, +} + +impl<'a> XtreamApiStreamRequest<'a> { + pub fn from(context: XtreamApiStreamContext, + username: &'a str, + password: &'a str, + stream_id: &'a str, + action_path: &'a str) -> Self { + Self { + context, + username, + password, + stream_id, + action_path, + } + } +} + +pub(crate) fn serve_query(file_path: &Path, filter: &HashMap<&str, &str>) -> HttpResponse { let filtered = json_utils::json_filter_file(file_path, filter); HttpResponse::Ok().json(filtered) } @@ -107,50 +152,6 @@ fn xtream_api_request_separate_number_and_rest(input: &str) -> (String, String) } } -enum XtreamApiStreamContext { - LiveAlt, - Live, - Movie, - Series, - Timeshift, -} - -impl Display for XtreamApiStreamContext { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", match self { - XtreamApiStreamContext::LiveAlt => "", - XtreamApiStreamContext::Live => "live", - XtreamApiStreamContext::Movie => "movie", - XtreamApiStreamContext::Series => "series", - XtreamApiStreamContext::Timeshift => "timeshift", - }) - } -} - -struct XtreamApiStreamRequest<'a> { - context: XtreamApiStreamContext, - username: &'a str, - password: &'a str, - stream_id: &'a str, - action_path: &'a str, -} - -impl<'a> XtreamApiStreamRequest<'a> { - pub fn from(context: XtreamApiStreamContext, - username: &'a str, - password: &'a str, - stream_id: &'a str, - action_path: &'a str) -> Self { - XtreamApiStreamRequest { - context, - username, - password, - stream_id, - action_path, - } - } -} - async fn xtream_player_api_stream( req: &HttpRequest, api_req: &web::Query, @@ -166,26 +167,24 @@ async fn xtream_player_api_stream( Err(_) => return HttpResponse::BadRequest().finish() }; - match xtream_get_item_for_stream_id(req_stream_id, &app_state.config, target, None) { - Ok(pli) => { - let input_id: u16 = pli.input_id; - if let Some(input) = app_state.config.get_input_by_id(&input_id) { - let mut query_path = if stream_req.action_path.is_empty() { "".to_string() } else { format!("{}/", stream_req.action_path) }; - query_path = format!("{}{}{}", query_path, pli.provider_id, stream_ext); - if let Some(stream_url) = get_xtream_player_api_stream_url(input, stream_req.context.to_string().as_str(), query_path.as_str()) { - if user.proxy == ProxyType::Redirect { - debug!("Redirecting stream request to {}", stream_url); - return HttpResponse::Found().insert_header(("Location", stream_url)).finish(); - } - return stream_response(&stream_url, req, Some(input)).await; - } else { - error!("Cant find stream url for target {}, context {}, stream_id {}", target_name, stream_req.context, req_stream_id); + if let Ok(pli) =xtream_get_item_for_stream_id(req_stream_id, &app_state.config, target, None) { + let input_id: u16 = pli.input_id; + if let Some(input) = app_state.config.get_input_by_id(&input_id) { + let mut query_path = if stream_req.action_path.is_empty() { String::new() } else { format!("{}/", stream_req.action_path) }; + query_path = format!("{query_path}{}{stream_ext}", pli.provider_id); + if let Some(stream_url) = get_xtream_player_api_stream_url(input, stream_req.context.to_string().as_str(), query_path.as_str()) { + if user.proxy == ProxyType::Redirect { + debug!("Redirecting stream request to {stream_url}"); + return HttpResponse::Found().insert_header(("Location", stream_url)).finish(); } - } else { - error!("Cant find input for target {}, context {}, stream_id {}", target_name, stream_req.context, req_stream_id); + return stream_response(&stream_url, req, Some(input)).await; } + error!("Cant find stream url for target {target_name}, context {}, stream_id {req_stream_id}", stream_req.context); + } else { + error!("Cant find input for target {target_name}, context {}, stream_id {req_stream_id}", stream_req.context); } - Err(_) => error!("Failed to read xtream item for stream id {}", req_stream_id), + } else { + error!("Failed to read xtream item for stream id {}", req_stream_id); } } else { debug!("Target has no xtream output {}", target_name); @@ -421,15 +420,14 @@ async fn xtream_player_api_handle_content_action(config: &Config, target_name: & if let Some(file_path) = path { let category_id = category_id.trim(); if !category_id.is_empty() { - return Some(serve_query(&file_path, &HashMap::from([("category_id", category_id)])).await); + return Some(serve_query(&file_path, &HashMap::from([("category_id", category_id)]))); } else { return Some(serve_file(&file_path, req, mime::APPLICATION_JSON).await); } } else if let Some(payload) = content { return Some(HttpResponse::Ok().body(payload)); - } else { - return Some(HttpResponse::NoContent().finish()); } + return Some(HttpResponse::NoContent().finish()); } None } diff --git a/src/filter.rs b/src/filter.rs index 5f7de60e9..9e954c902 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -30,7 +30,7 @@ pub(crate) fn set_field_value(pli: &mut PlaylistItem, field: &ItemField, value: ItemField::Group => header.group = value, ItemField::Name => header.name = value, ItemField::Title => header.title = value, - ItemField::Url => header.url = value, + ItemField::Url => header.url = value, }; } @@ -162,16 +162,16 @@ impl std::fmt::Display for Filter { write!(f, "{} ~ \"{}\"", field, String::from(&rewc.restr)) } Filter::Group(stmt) => { - write!(f, "({})", stmt) + write!(f, "({stmt})") } Filter::UnaryExpression(op, expr) => { let flt = match op { - UnaryOperator::Not => format!("NOT {}", expr), + UnaryOperator::Not => format!("NOT {expr}"), }; write!(f, "{}", flt) } Filter::BinaryExpression(left, op, right) => { - write!(f, "{} {} {}", left, op, right) + write!(f, "{left} {op} {right}") } } } @@ -238,12 +238,12 @@ macro_rules! handle_expr { let lhs = $stmts.pop().unwrap(); $bop = None; Filter::BinaryExpression(Box::new(lhs), binop.clone(), Box::new($exp)) - }, + } _ => match $uop { Some(unop) => { $uop = None; Filter::UnaryExpression(unop.clone(), Box::new($exp)) - }, + } _ => $exp } }; @@ -393,7 +393,7 @@ fn build_dependency_graph(templates: &Vec) -> GraphDependency { }; for template in templates { - let node_idx = add_node(&mut graph, &template.name); + let node_index = add_node(&mut graph, &template.name); let edges = regex.captures_iter(&template.value) .filter(|caps| caps.len() > 1) .filter_map(|caps| caps.get(1)) @@ -402,7 +402,7 @@ fn build_dependency_graph(templates: &Vec) -> GraphDependency { let iter = edges.iter(); for edge in iter { let edge_idx = add_node(&mut graph, edge); - graph.add_edge(edge_idx, node_idx, ()); + graph.add_edge(edge_idx, node_index, ()); } node_deps.insert(&template.name, edges); } @@ -423,33 +423,33 @@ pub(crate) fn prepare_templates(templates: &Vec) -> Result = templates.iter().map(|t| (&t.name, t.value.clone())).collect(); - // Perform a topological sort to get a linear ordering of the nodes - let node_indices = toposort(&graph, None).unwrap(); - let indices = node_indices.iter(); - for node in indices { - // only nodes with dependencies - if graph.edges_directed(*node, petgraph::Incoming).count() > 0 { - let node_name = node_map.get(&node.index()).unwrap(); - if let Some(deps) = node_deps.get(node_name) { - if log_enabled!(Level::Debug) { - debug!("template {} depends on [{}]", node_name, deps.join(", ")); - } - let mut node_template = dep_value_map.get(node_name).unwrap().clone(); - for dep_name in deps { - let dep_template = dep_value_map.get(dep_name).unwrap().clone(); - let new_templ = node_template.replace(format!("!{}!", dep_name).as_str(), &dep_template); - node_template = new_templ; - } - dep_value_map.insert(node_name, String::from(&node_template)); - let template = result.iter_mut().find(|t| node_name.eq(&t.name)).unwrap(); - //let new_value = dep_value_map.get(&template.name).unwrap(); - template.value = String::from(&node_template); + } + let mut dep_value_map: HashMap<&String, String> = templates.iter().map(|t| (&t.name, t.value.clone())).collect(); + // Perform a topological sort to get a linear ordering of the nodes + let node_indices = toposort(&graph, None).unwrap(); + let indices = node_indices.iter(); + for node in indices { + // only nodes with dependencies + if graph.edges_directed(*node, petgraph::Incoming).count() > 0 { + let node_name = node_map.get(&node.index()).unwrap(); + if let Some(deps) = node_deps.get(node_name) { + if log_enabled!(Level::Debug) { + debug!("template {} depends on [{}]", node_name, deps.join(", ")); } + let mut node_template = dep_value_map.get(node_name).unwrap().clone(); + for dep_name in deps { + let dep_template = dep_value_map.get(dep_name).unwrap().clone(); + let new_templ = node_template.replace(format!("!{}!", dep_name).as_str(), &dep_template); + node_template = new_templ; + } + dep_value_map.insert(node_name, String::from(&node_template)); + let template = result.iter_mut().find(|t| node_name.eq(&t.name)).unwrap(); + //let new_value = dep_value_map.get(&template.name).unwrap(); + template.value = String::from(&node_template); } } } + if log_enabled!(Level::Debug) { debug!("{:#?}", result); } diff --git a/src/main.rs b/src/main.rs index 1d5d1ebfd..ae5f62e0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,8 +86,8 @@ fn main() { if args.genpwd { match generate_password() { - Ok(pwd) => println!("{}", pwd), - Err(err) => error!("{}", err) + Ok(pwd) => println!("{pwd}"), + Err(err) => error!("{err}") } return; } @@ -117,7 +117,7 @@ fn main() { config_reader::read_api_proxy_config(args.api_proxy, &mut cfg); start_in_server_mode(Arc::new(cfg), Arc::new(targets)); } else { - start_in_cli_mode(Arc::new(cfg), Arc::new(targets)) + start_in_cli_mode(Arc::new(cfg), Arc::new(targets)); } } @@ -127,11 +127,8 @@ fn start_in_cli_mode(cfg: Arc, targets: Arc) { fn start_in_server_mode(cfg: Arc, targets: Arc) { info!("Server running: http://{}:{}", &cfg.api.host, &cfg.api.port); - match api::main_api::start_server(cfg, targets) { - Ok(_) => {} - Err(e) => { - exit!("Can't start server: {}", e); - } + if let Err(err) = api::main_api::start_server(cfg, targets) { + exit!("Can't start server: {err}"); }; } @@ -139,9 +136,9 @@ fn get_log_level(log_level: &str) -> LevelFilter { match log_level.to_lowercase().as_str() { "trace" => LevelFilter::Trace, "debug" => LevelFilter::Debug, - "info" => LevelFilter::Info, "warn" => LevelFilter::Warn, "error" => LevelFilter::Error, + // "info" => LevelFilter::Info, _ => LevelFilter::Info, } } diff --git a/src/model/api_proxy.rs b/src/model/api_proxy.rs index 1cb9a18a3..cb429059a 100644 --- a/src/model/api_proxy.rs +++ b/src/model/api_proxy.rs @@ -16,17 +16,17 @@ pub(crate) enum ProxyType { Redirect, } -impl ProxyType { - fn default() -> ProxyType { - ProxyType::Redirect +impl Default for ProxyType { + fn default() -> Self { + Self::Redirect } } impl Display for ProxyType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", match self { - ProxyType::Reverse => "reverse", - ProxyType::Redirect => "redirect" + Self::Reverse => "reverse", + Self::Redirect => "redirect" }) } } @@ -36,9 +36,9 @@ impl FromStr for ProxyType { fn from_str(s: &str) -> Result { if s.eq("reverse") { - Ok(ProxyType::Reverse) + Ok(Self::Reverse) } else if s.eq("redirect") { - Ok(ProxyType::Redirect) + Ok(Self::Redirect) } else { create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "Unknown ProxyType: {}", s) } diff --git a/src/model/config.rs b/src/model/config.rs index 61daade21..a24d493e3 100644 --- a/src/model/config.rs +++ b/src/model/config.rs @@ -1,4 +1,3 @@ -use std::rc::Rc; use enum_iterator::Sequence; use std::borrow::BorrowMut; use std::collections::{HashMap, HashSet}; @@ -20,15 +19,14 @@ use crate::model::api_proxy::{ApiProxyConfig, ProxyUserCredentials}; use crate::model::mapping::Mapping; use crate::model::mapping::Mappings; use crate::utils::{config_reader, file_utils}; +use crate::utils::default_utils::{default_as_default, default_as_false, default_as_true, + default_as_empty_list, default_as_frm, default_as_empty_map, + default_as_zero_u8, default_as_two_u16}; pub(crate) const MAPPER_ATTRIBUTE_FIELDS: &[&str] = &[ "name", "title", "group", "id", "logo", - "logo_small", - "parent_code", - "audio_track", - "time_shift", - "rec", - "url", + "logo_small", "parent_code", "audio_track", + "time_shift", "rec", "url", ]; pub(crate) const AFFIX_FIELDS: &[&str] = &["name", "title", "group"]; @@ -39,25 +37,6 @@ macro_rules! valid_property { }}; } -pub(crate) fn default_as_true() -> bool { true } - -pub(crate) fn default_as_false() -> bool { false } - -pub(crate) fn default_as_empty_str() -> String { String::from("") } - -pub(crate) fn default_as_empty_rc_str() -> Rc { Rc::new(String::from("")) } - -fn default_as_zero_u8() -> u8 { 0 } - -fn default_as_frm() -> ProcessingOrder { ProcessingOrder::Frm } - -pub(crate) fn default_as_default() -> String { String::from("default") } - -pub(crate) fn default_as_empty_map() -> HashMap { HashMap::new() } - -pub(crate) fn default_as_empty_list() -> Vec { vec![] } - - #[macro_export] macro_rules! create_m3u_filter_error_result { ($kind: expr, $($arg:tt)*) => { @@ -103,12 +82,12 @@ pub(crate) enum TargetType { Xtream, } -impl std::fmt::Display for TargetType { +impl Display for TargetType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { - TargetType::M3u => write!(f, "M3u"), - TargetType::Strm => write!(f, "Strm"), - TargetType::Xtream => write!(f, "Xtream"), + Self::M3u => write!(f, "M3u"), + Self::Strm => write!(f, "Strm"), + Self::Xtream => write!(f, "Xtream"), } } } @@ -132,12 +111,12 @@ pub(crate) enum ProcessingOrder { impl std::fmt::Display for ProcessingOrder { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { - ProcessingOrder::Frm => write!(f, "filter, rename, map"), - ProcessingOrder::Fmr => write!(f, "filter, map, rename"), - ProcessingOrder::Rfm => write!(f, "rename, filter, map"), - ProcessingOrder::Rmf => write!(f, "rename, map, filter"), - ProcessingOrder::Mfr => write!(f, "map, filter, rename"), - ProcessingOrder::Mrf => write!(f, "map, rename, filter"), + Self::Frm => write!(f, "filter, rename, map"), + Self::Fmr => write!(f, "filter, map, rename"), + Self::Rfm => write!(f, "rename, filter, map"), + Self::Rmf => write!(f, "rename, map, filter"), + Self::Mfr => write!(f, "map, filter, rename"), + Self::Mrf => write!(f, "map, rename, filter"), } } } @@ -154,13 +133,13 @@ pub(crate) enum ItemField { Url, } -impl std::fmt::Display for ItemField { +impl Display for ItemField { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { - ItemField::Group => write!(f, "Group"), - ItemField::Name => write!(f, "Name"), - ItemField::Title => write!(f, "Title"), - ItemField::Url => write!(f, "Url"), + Self::Group => write!(f, "Group"), + Self::Name => write!(f, "Name"), + Self::Title => write!(f, "Title"), + Self::Url => write!(f, "Url"), } } } @@ -263,7 +242,6 @@ impl ConfigRename { } } -fn default_as_two() -> u16 { 2 } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub(crate) struct ConfigTargetOptions { @@ -283,7 +261,7 @@ pub(crate) struct ConfigTargetOptions { pub xtream_skip_series_direct_source: bool, #[serde(default = "default_as_false")] pub xtream_resolve_series: bool, - #[serde(default = "default_as_two")] + #[serde(default = "default_as_two_u16")] pub xtream_resolve_series_delay: u16, } @@ -935,9 +913,8 @@ impl Config { if !default_target_name.eq_ignore_ascii_case(target_name.as_str()) { if target_names_check.contains(target_name.as_str()) { return create_m3u_filter_error_result!(M3uFilterErrorKind::Info, "target names should be unique: {}", target_name, ); - } else { - target_names_check.insert(target_name); } + target_names_check.insert(target_name); } // prepare templaes let prepare_result = match &self.templates { diff --git a/src/model/mapping.rs b/src/model/mapping.rs index 1d9e8217d..bf7272b70 100644 --- a/src/model/mapping.rs +++ b/src/model/mapping.rs @@ -8,9 +8,10 @@ use regex::Regex; use crate::{handle_m3u_filter_error_result, valid_property}; use crate::filter::{Filter, get_filter, PatternTemplate, prepare_templates, RegexWithCaptures, ValueProcessor}; use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind}; -use crate::model::config::{AFFIX_FIELDS, default_as_empty_map, default_as_empty_str, - default_as_false, ItemField, MAPPER_ATTRIBUTE_FIELDS, }; +use crate::model::config::{AFFIX_FIELDS, ItemField, MAPPER_ATTRIBUTE_FIELDS, }; use crate::model::playlist::{FieldAccessor, PlaylistItem}; +use crate::utils::default_utils::{default_as_empty_map, default_as_empty_str, + default_as_false}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub(crate) struct MappingTag { diff --git a/src/model/playlist.rs b/src/model/playlist.rs index 13f8ac9f6..f65164977 100644 --- a/src/model/playlist.rs +++ b/src/model/playlist.rs @@ -6,9 +6,9 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::model::config::{ConfigInput, ConfigTarget}; -use crate::model::config::{default_as_false}; use crate::model::xmltv::TVGuide; use crate::model::xtream::{xtream_playlistitem_to_document, XtreamMappingOptions}; +use crate::utils::default_utils::{default_as_false, default_as_zero_u16, default_as_zero_u32, default_playlist_item_type, default_stream_cluster}; // https://de.wikipedia.org/wiki/M3U // https://siptv.eu/howto/playlist.html @@ -48,8 +48,6 @@ impl Display for XtreamCluster { } } -pub(crate) fn default_stream_cluster() -> XtreamCluster { XtreamCluster::Live } - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub(crate) enum PlaylistItemType { Live = 1, @@ -58,10 +56,6 @@ pub(crate) enum PlaylistItemType { SeriesInfo = 4, } -pub(crate) fn default_playlist_item_type() -> PlaylistItemType { PlaylistItemType::Live } -fn default_as_zero_u32() -> u32 { 0 } -fn default_as_zero_u16() -> u16 { 0 } - pub(crate) trait FieldAccessor { fn get_field(&self, field: &str) -> Option>; fn set_field(&mut self, field: &str, value: &str) -> bool; @@ -280,7 +274,7 @@ impl PlaylistItem { item_type: header.item_type.clone(), series_fetched: header.series_fetched, category_id: header.category_id, - input_id: header.input_id + input_id: header.input_id, } } } diff --git a/src/model/xmltv.rs b/src/model/xmltv.rs index 66258872c..d6ce91128 100644 --- a/src/model/xmltv.rs +++ b/src/model/xmltv.rs @@ -5,7 +5,6 @@ use quick_xml::{Error, Writer}; // https://github.com/XMLTV/xmltv/blob/master/xmltv.dtd - #[derive(Debug, Clone)] pub(crate) struct XmlTag { pub name: String, diff --git a/src/model/xtream.rs b/src/model/xtream.rs index fb8eadd86..e83c6c518 100644 --- a/src/model/xtream.rs +++ b/src/model/xtream.rs @@ -1,13 +1,14 @@ use std::collections::HashMap; -use std::iter::{FromIterator}; +use std::iter::FromIterator; use std::rc::Rc; use serde::{Deserialize, Deserializer, Serialize}; use serde::de::DeserializeOwned; use serde_json::{Map, Value}; -use crate::model::config::{ConfigTargetOptions, default_as_empty_rc_str}; +use crate::model::config::ConfigTargetOptions; use crate::model::playlist::{PlaylistItem, XtreamCluster, XtreamPlaylistItem}; +use crate::utils::default_utils::{default_as_empty_rc_str, default_as_empty_list}; const LIVE_STREAM_FIELDS: &[&str] = &[]; @@ -24,8 +25,6 @@ const SERIES_STREAM_FIELDS: &[&str] = &[ "stream_type", "title", "year", "youtube_trailer", ]; -fn default_as_empty_list() -> Vec { vec![] } - fn deserialize_number_from_string<'de, D, T: DeserializeOwned>( deserializer: D, ) -> Result, D::Error> @@ -360,7 +359,7 @@ impl XtreamMappingOptions { let (skip_live_direct_source, skip_video_direct_source, skip_series_direct_source) = options .map_or((false, false, false), |o| (o.xtream_skip_live_direct_source, o.xtream_skip_video_direct_source, o.xtream_skip_series_direct_source)); - XtreamMappingOptions { + Self { skip_live_direct_source, skip_video_direct_source, skip_series_direct_source, diff --git a/src/processing/m3u_parser.rs b/src/processing/m3u_parser.rs index 34ae22306..3ce19cc72 100644 --- a/src/processing/m3u_parser.rs +++ b/src/processing/m3u_parser.rs @@ -1,9 +1,10 @@ -use std::borrow::{BorrowMut}; +use std::borrow::BorrowMut; use std::cell::RefCell; use std::rc::Rc; + use crate::model::config::{Config, ConfigInput}; -use crate::model::config::default_as_empty_rc_str; -use crate::model::playlist::{default_playlist_item_type, default_stream_cluster, PlaylistGroup, PlaylistItem, PlaylistItemHeader, PlaylistItemType, XtreamCluster}; +use crate::model::playlist::{PlaylistGroup, PlaylistItem, PlaylistItemHeader, PlaylistItemType, XtreamCluster}; +use crate::utils::default_utils::{default_as_empty_rc_str, default_playlist_item_type, default_stream_cluster}; use crate::utils::string_utils; fn token_value(it: &mut std::str::Chars) -> String { @@ -73,7 +74,7 @@ fn create_empty_playlistitem_header(input_id: u16, url: String) -> PlaylistItemH additional_properties: None, series_fetched: false, category_id: 0, - input_id + input_id, } } diff --git a/src/processing/playlist_processor.rs b/src/processing/playlist_processor.rs index b85d958cd..bcb2b3db8 100644 --- a/src/processing/playlist_processor.rs +++ b/src/processing/playlist_processor.rs @@ -15,7 +15,7 @@ use crate::{Config, get_errors_notify_message, model::config}; use crate::filter::{get_field_value, MockValueProcessor, set_field_value, ValueProvider}; use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind}; use crate::messaging::{MsgKind, send_message}; -use crate::model::config::{ConfigSortChannel, ConfigSortGroup, ConfigTarget, default_as_default, InputType, +use crate::model::config::{ConfigSortChannel, ConfigSortGroup, ConfigTarget, InputType, ItemField, ProcessingOrder, ProcessTargets, SortOrder::{Asc, Desc}}; use crate::model::mapping::{Mapping, MappingValueProcessor}; use crate::model::playlist::{FetchedPlaylist, PlaylistGroup, PlaylistItem}; @@ -26,6 +26,7 @@ use crate::processing::xmltv_parser::flatten_tvguide; use crate::processing::xtream_processor::playlist_resolve_series; use crate::repository::playlist_repository::persist_playlist; use crate::utils::download; +use crate::utils::default_utils:: default_as_default; fn is_valid(pli: &PlaylistItem, target: &ConfigTarget) -> bool { let provider = ValueProvider { pli: RefCell::new(pli) }; diff --git a/src/processing/xmltv_parser.rs b/src/processing/xmltv_parser.rs index 33253b8cb..8d930cb12 100644 --- a/src/processing/xmltv_parser.rs +++ b/src/processing/xmltv_parser.rs @@ -4,6 +4,10 @@ use quick_xml::events::Event; use quick_xml::Reader; use crate::model::xmltv::{Epg, TVGuide, XmlTag}; +static EPG_PROGRAMME: &str = "programme"; +static EPG_CHANNEL: &str = "channel"; +static EPG_ID: &str = "id"; + pub(crate) fn parse_tvguide(content: &str) -> Option { let mut stack: Vec = vec![]; let mut reader = Reader::from_str(content); @@ -79,8 +83,8 @@ pub(crate) fn flatten_tvguide(tv_guides: &[Epg]) -> Option { epg.attributes.clone_from(&guide.attributes); } guide.children.iter().for_each(|c| { - if c.name.as_str() == "channel" { - if let Some(chan_id) = c.get_attribute_value("id") { + if c.name.as_str() == EPG_CHANNEL { + if let Some(chan_id) = c.get_attribute_value(EPG_ID) { if !channel_ids.contains(&chan_id) { channel_ids.push(chan_id); epg.children.push(c.clone()) @@ -89,8 +93,8 @@ pub(crate) fn flatten_tvguide(tv_guides: &[Epg]) -> Option { } }); guide.children.iter().for_each(|c| { - if c.name.as_str() == "programme" { - if let Some(chan_id) = c.get_attribute_value("channel") { + if c.name.as_str() == EPG_PROGRAMME { + if let Some(chan_id) = c.get_attribute_value(EPG_CHANNEL) { if channel_ids.contains(&chan_id) { epg.children.push(c.clone()); } diff --git a/src/processing/xtream_parser.rs b/src/processing/xtream_parser.rs index 200d84ca2..abb1e3591 100644 --- a/src/processing/xtream_parser.rs +++ b/src/processing/xtream_parser.rs @@ -3,13 +3,13 @@ use std::collections::HashMap; use std::rc::Rc; use serde_json::Value; -use crate::{create_m3u_filter_error_result}; + +use crate::create_m3u_filter_error_result; use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind}; use crate::model::config::ConfigInput; - -use crate::model::config::{default_as_empty_rc_str}; use crate::model::playlist::{PlaylistGroup, PlaylistItem, PlaylistItemHeader, PlaylistItemType, XtreamCluster}; use crate::model::xtream::{XtreamCategory, XtreamSeriesInfo, XtreamStream}; +use crate::utils::default_utils::default_as_empty_rc_str; fn map_to_xtream_category(category: &Value) -> Result, M3uFilterError> { match serde_json::from_value::>(category.to_owned()) { @@ -63,7 +63,7 @@ pub(crate) fn parse_xtream_series_info(info: &Value, group_title: &str, input: & additional_properties: episode.get_additional_properties(&series_info), series_fetched: false, category_id: 0, - input_id: input.id + input_id: input.id, }) }).collect(); if result.is_empty() { Ok(None) } else { Ok(Some(result)) } @@ -134,7 +134,7 @@ pub(crate) fn parse_xtream(input: &ConfigInput, additional_properties: stream.get_additional_properties(), series_fetched: false, category_id: 0, - input_id + input_id, }), }; grp.add(item); diff --git a/src/repository/indexed_document_reader.rs b/src/repository/indexed_document_reader.rs index 4f18b8e6c..d7e8f8687 100644 --- a/src/repository/indexed_document_reader.rs +++ b/src/repository/indexed_document_reader.rs @@ -28,7 +28,7 @@ impl IndexedDocumentReader { } Err(_e) => 0, }; - Ok(IndexedDocumentReader { + Ok(Self { main_file, index_file, cursor: 0, diff --git a/src/repository/indexed_document_writer.rs b/src/repository/indexed_document_writer.rs index 6d6a18565..01c645832 100644 --- a/src/repository/indexed_document_writer.rs +++ b/src/repository/indexed_document_writer.rs @@ -27,7 +27,7 @@ impl IndexedDocumentWriter { Ok(meta) => meta.len() as u32, Err(_) => 0 }; - Ok(IndexedDocumentWriter { + Ok(Self { main_path, index_path, main_file, diff --git a/src/repository/kodi_repository.rs b/src/repository/kodi_repository.rs index 4b6c44a62..c36c22584 100644 --- a/src/repository/kodi_repository.rs +++ b/src/repository/kodi_repository.rs @@ -99,7 +99,7 @@ pub(crate) fn kodi_write_strm_playlist(target: &ConfigTarget, cfg: &Config, new_ error!("cant create directory: {:?}", &path); return create_m3u_filter_error_result!(M3uFilterErrorKind::Notify, "failed to write strm playlist: {}", e); }; - let mut file_name = sanitize_for_filename(&header.title, underscore_whitespace); + let mut kodi_file_name = sanitize_for_filename(&header.title, underscore_whitespace); if kodi_style { let style = KodiStyle { season: regex::Regex::new(r"[Ss]\d\d").unwrap(), @@ -107,9 +107,9 @@ pub(crate) fn kodi_write_strm_playlist(target: &ConfigTarget, cfg: &Config, new_ year: regex::Regex::new(r"\d\d\d\d").unwrap(), whitespace: regex::Regex::new(r"\s+").unwrap(), }; - file_name = kodi_style_rename(&file_name, &style); + kodi_file_name = kodi_style_rename(&kodi_file_name, &style); } - let file_path = dir_path.join(format!("{}.strm", file_name)); + let file_path = dir_path.join(format!("{}.strm", kodi_file_name)); match File::create(&file_path) { Ok(mut strm_file) => { match file_utils::check_write(strm_file.write_all(header.url.as_bytes())) { diff --git a/src/utils/config_reader.rs b/src/utils/config_reader.rs index 582d50b32..0d0c2a61c 100644 --- a/src/utils/config_reader.rs +++ b/src/utils/config_reader.rs @@ -53,8 +53,8 @@ pub(crate) fn read_config(config_path: &str, config_file: &str, sources_file: &s result._config_file_path = config_file.to_string(); result._sources_file_path = sources_file.to_string(); match result.prepare(true) { - Ok(_) => Ok(result), - Err(err) => Err(err) + Err(err) => Err(err), + _ => Ok(result), } } Err(e) => { @@ -68,25 +68,15 @@ pub(crate) fn read_config(config_path: &str, config_file: &str, sources_file: &s pub(crate) fn read_mapping(mapping_file: &str) -> Result, M3uFilterError> { let mapping_file = std::path::PathBuf::from(mapping_file); - match file_utils::open_file(&mapping_file) { - Ok(file) => { - let mapping: Result = serde_yaml::from_reader(file); - match mapping { - Ok(mut result) => { - handle_m3u_filter_error_result!(M3uFilterErrorKind::Info, result.prepare()); - Ok(Some(result)) - } - Err(err) => { - error!("cant read mapping file: {}", err); - Ok(None) - } - } - } - _ => { - warn!("cant read mapping file: {}", mapping_file.to_str().unwrap_or("?")); - Ok(None) + if let Ok(file) = file_utils::open_file(&mapping_file) { + let mapping: Result = serde_yaml::from_reader(file); + if let Ok(mut result) = mapping { + handle_m3u_filter_error_result!(M3uFilterErrorKind::Info, result.prepare()); + return Ok(Some(result)); } } + warn!("cant read mapping file: {}", mapping_file.to_str().unwrap_or("?")); + Ok(None) } pub(crate) fn read_api_proxy(api_proxy_file: &str, resolve_var: bool) -> Option { @@ -96,13 +86,13 @@ pub(crate) fn read_api_proxy(api_proxy_file: &str, resolve_var: bool) -> Option< match mapping { Ok(mut result) => { match result.prepare(resolve_var) { - Ok(_) => { - Some(result) - } Err(err) => { error!("cant read api-proxy-config file: {}", err); None } + _ => { + Some(result) + } } } Err(err) => { diff --git a/src/utils/default_utils.rs b/src/utils/default_utils.rs new file mode 100644 index 000000000..758436c94 --- /dev/null +++ b/src/utils/default_utils.rs @@ -0,0 +1,30 @@ +use std::collections::HashMap; +use std::rc::Rc; +use crate::model::config::ProcessingOrder; +use crate::model::playlist::{PlaylistItemType, XtreamCluster}; + +pub(crate) fn default_as_true() -> bool { true } + +pub(crate) fn default_as_false() -> bool { false } + +pub(crate) fn default_as_empty_str() -> String { String::new() } + +pub(crate) fn default_as_empty_rc_str() -> Rc { Rc::new(String::new()) } + +pub(crate) fn default_as_zero_u8() -> u8 { 0 } + +pub(crate) fn default_as_frm() -> ProcessingOrder { ProcessingOrder::Frm } + +pub(crate) fn default_as_default() -> String { String::from("default") } + +pub(crate) fn default_as_empty_map() -> HashMap { HashMap::new() } + +pub(crate) fn default_as_empty_list() -> Vec { vec![] } + +pub(crate) fn default_as_two_u16() -> u16 { 2 } + +pub(crate) fn default_playlist_item_type() -> PlaylistItemType { PlaylistItemType::Live } +pub(crate) fn default_as_zero_u32() -> u32 { 0 } +pub(crate) fn default_as_zero_u16() -> u16 { 0 } + +pub(crate) fn default_stream_cluster() -> XtreamCluster { XtreamCluster::Live } diff --git a/src/utils/file_reader.rs b/src/utils/file_reader.rs index 27e2104b1..5d761c9f6 100644 --- a/src/utils/file_reader.rs +++ b/src/utils/file_reader.rs @@ -6,8 +6,8 @@ pub(crate) struct FileReader { } impl FileReader { - pub(crate) fn new(file: File) -> FileReader { - FileReader { + pub(crate) fn new(file: File) -> Self { + Self { reader: LineReader::new(file), } } diff --git a/src/utils/file_utils.rs b/src/utils/file_utils.rs index 7a46a65ca..f5b44bcf4 100644 --- a/src/utils/file_utils.rs +++ b/src/utils/file_utils.rs @@ -1,7 +1,8 @@ use std::fs; use std::fs::{File, OpenOptions}; -use std::io::{Write}; +use std::io::Write; use std::path::{Path, PathBuf}; + use log::{debug, error}; use path_absolutize::*; @@ -47,28 +48,41 @@ pub(crate) fn get_default_file_path(config_path: &str, file: &str) -> String { }) } +static USER_FILE: &str = "user.txt"; +static CONFIG_PATH: &str = "config"; +static CONFIG_FILE: &str = "config.yml"; +static SOURCE_FILE: &str = "source.yml"; +static MAPPING_FILE: &str = "mapping.yml"; +static API_PROXY_FILE: &str = "api-proxy.yml"; + +#[inline] pub(crate) fn get_default_user_file_path(config_path: &str) -> String { - get_default_file_path(config_path, "user.txt") + get_default_file_path(config_path, USER_FILE) } +#[inline] pub(crate) fn get_default_config_path() -> String { - get_default_path("config") + get_default_path(CONFIG_PATH) } +#[inline] pub(crate) fn get_default_config_file_path(config_path: &str) -> String { - get_default_file_path(config_path, "config.yml") + get_default_file_path(config_path, CONFIG_FILE) } +#[inline] pub(crate) fn get_default_sources_file_path(config_path: &str) -> String { - get_default_file_path(config_path, "source.yml") + get_default_file_path(config_path, SOURCE_FILE) } +#[inline] pub(crate) fn get_default_mappings_path(config_path: &str) -> String { - get_default_file_path(config_path, "mapping.yml") + get_default_file_path(config_path, MAPPING_FILE) } +#[inline] pub(crate) fn get_default_api_proxy_config_path(config_path: &str) -> String { - get_default_file_path(config_path, "api-proxy.yml") + get_default_file_path(config_path, API_PROXY_FILE) } pub(crate) fn get_working_path(wd: &String) -> String { @@ -106,14 +120,15 @@ pub(crate) fn get_working_path(wd: &String) -> String { } } -pub(crate) fn open_file(file_name: &Path) -> Result { - fs::File::open(file_name) +#[inline] +pub(crate) fn open_file(file_name: &Path) -> Result { + File::open(file_name) } pub(crate) fn persist_file(persist_file: Option, text: &String) { if let Some(path_buf) = persist_file { let filename = &path_buf.to_str().unwrap_or("?"); - match fs::File::create(&path_buf) { + match File::create(&path_buf) { Ok(mut file) => match file.write_all(text.as_bytes()) { Ok(_) => debug!("persisted: {}", filename), Err(e) => error!("failed to persist file {}, {}", filename, e) @@ -125,8 +140,8 @@ pub(crate) fn persist_file(persist_file: Option, text: &String) { pub(crate) fn prepare_persist_path(file_name: &str, date_prefix: &str) -> Option { let now = chrono::Local::now(); - let filename = file_name.replace("{}", format!("{}{}", date_prefix, now.format("%Y%m%d_%H%M%S").to_string().as_str()).as_str()); - Some(std::path::PathBuf::from(filename)) + let persist_filename = file_name.replace("{}", format!("{date_prefix}{}", now.format("%Y%m%d_%H%M%S").to_string().as_str()).as_str()); + Some(std::path::PathBuf::from(persist_filename)) } pub(crate) fn get_file_path(wd: &String, path: Option) -> Option { @@ -174,7 +189,7 @@ pub(crate) fn check_write(res: std::io::Result<()>) -> Result<(), std::io::Error } pub(crate) fn open_file_append(path: &Path, append: bool) -> Result { - if append && path.exists() { + if append && path.exists() { return OpenOptions::new() .append(true) // Open in append mode .open(path); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index aa7bb018c..43590d89b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,5 +4,6 @@ pub (crate) mod download; pub (crate) mod string_utils; pub (crate) mod json_utils; pub (crate) mod config_reader; +pub (crate) mod default_utils; pub (crate) mod multi_file_reader; -// pub (crate) mod file_reader; \ No newline at end of file +// pub (crate) mod file_reader; diff --git a/src/utils/multi_file_reader.rs b/src/utils/multi_file_reader.rs index 5f9c0581a..c64a14e4b 100644 --- a/src/utils/multi_file_reader.rs +++ b/src/utils/multi_file_reader.rs @@ -33,7 +33,7 @@ impl Read for MultiFileReader { if self.files.is_empty() { return Ok(0); } - self.current_reader = Some(BufReader::new(self.files.pop().unwrap())) + self.current_reader = Some(BufReader::new(self.files.pop().unwrap())); } let reader = self.current_reader.as_mut().unwrap(); match reader.read(buf) { diff --git a/src/utils/string_utils.rs b/src/utils/string_utils.rs index 53136c400..3fc08b113 100644 --- a/src/utils/string_utils.rs +++ b/src/utils/string_utils.rs @@ -3,9 +3,9 @@ pub (crate) fn get_title_group(text: &str) -> String { let alphabetic_only: String = text.chars().map(|c| if c.is_alphanumeric() { c } else { ' ' }).collect(); let parts = alphabetic_only.split_whitespace(); - let mut combination = "".to_string(); - for p in parts.into_iter() { - combination = format!("{} {}", combination, p).trim().to_string(); + let mut combination = String::new(); + for p in parts { + combination = format!("{combination} {p}").trim().to_string(); if combination.len() > 2 { return combination; }