From 392a90ca2a99e6b8b568f5b1aced7a4c8f84096a Mon Sep 17 00:00:00 2001 From: euzu Date: Tue, 5 Nov 2024 15:58:50 +0100 Subject: [PATCH] code refactoring/optimization --- README.md | 23 ++++++++++---------- TODO.md | 1 - src/api/download_api.rs | 2 +- src/model/stats.rs | 5 +---- src/processing/playlist_watch.rs | 6 +++--- src/utils/download.rs | 3 ++- src/utils/file_lock_manager.rs | 5 +---- src/utils/request_utils.rs | 37 ++++++++++++++++---------------- 8 files changed, 38 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index def19d91b..cf0d699af 100644 --- a/README.md +++ b/README.md @@ -4,26 +4,25 @@ ![m3u-filter_banner](https://github.com/user-attachments/assets/ea10bc02-eb2d-415f-828f-a14f9b57f5e8) -**m3u-filter** is a simple application that can: +**m3u-filter** is a versatile tool for processing playlists. Key capabilities include: -- Filter, rename, map, and sort entries from a playlist, and save them in EXTM3U, XTREAM, or Kodi format. +- Filtering, renaming, mapping, and sorting playlist entries and saving them in EXTM3U, XTREAM, or Kodi formats. - Process multiple input files and create multiple output files through target definitions. - Act as a simple Xtream or M3U server after processing entries. - Serve as a redirect or reverse proxy for Xtream. - Schedule updates in server mode. -- Run as a CLI command to serve processed playlists via web servers like Nginx or Apache. +- Running as a CLI tool to deliver playlists through web servers (e.g., Nginx, Apache). - Define multiple filtering targets to create several playlists from a large one. -- Use regular expressions for matching. -- Define filters with statements, e.g.: filter: - - `(Group ~ "^FR.*") AND NOT (Group ~ ".*XXX.*" OR Group ~ ".*SERIES.*" OR Group ~ ".*MOVIES.*")` +- Using regular expressions for matching and defining templates for reusability. - DRY (Don't Repeat Yourself): Define templates and reuse them. -- Send a Telegram bot or REST message when something goes wrong. -- Monitor changes in groups and send notifications on detected changes. +- Define filters with statements, e.g.: filter: + `(Group ~ "^FR.*") AND NOT (Group ~ ".*XXX.*" OR Group ~ ".*SERIES.*" OR Group ~ ".*MOVIES.*")` +- Sending alerts via Telegram bot or REST when issues arise. +- Monitoring group changes and sending notifications. -If you have a playlist containing unwanted entries, you can create filters to include or exclude entries based on the playlist's header information. You can also rename or map entries using regular expressions. - -You can run m3u-filter as a command-line application to update your playlists (manually or as a cron job), or run it in server mode to open a web UI. From there, you can view the playlist contents, filter or search entries, and save filtered groups as a new playlist. +If you need to exclude certain entries from a playlist, you can create filters using headers and apply regex-based renaming or mapping. +Run `m3u-filter` as a CLI or in server mode for a web-based UI to manage playlist content and save filtered groups. +From the Web-UI, you can view the playlist contents, filter or search entries. ![m3u-filter_function](https://github.com/user-attachments/assets/1b5ba462-712a-4f41-9140-8cca913ba5f4) diff --git a/TODO.md b/TODO.md index 277323d9e..2284aa5de 100644 --- a/TODO.md +++ b/TODO.md @@ -11,4 +11,3 @@ ## Open topics - web ui should fetch series info when expanded -- Each update can change the stream id which can be confusing for players which save the state with the streamid \ No newline at end of file diff --git a/src/api/download_api.rs b/src/api/download_api.rs index 4dff741c7..c5b7a744a 100644 --- a/src/api/download_api.rs +++ b/src/api/download_api.rs @@ -62,7 +62,7 @@ fn run_download_queue(download_cfg: &VideoDownloadConfig, download_queue: &Arc { diff --git a/src/model/stats.rs b/src/model/stats.rs index dad022aa7..0709cd874 100644 --- a/src/model/stats.rs +++ b/src/model/stats.rs @@ -46,9 +46,6 @@ pub struct InputStats { impl Display for InputStats { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match serde_json::to_string(&self) { - Ok(json_str) => write!(f, "{json_str}"), - Err(_) => Err(std::fmt::Error), - } + serde_json::to_string(&self).map_or(Err(std::fmt::Error), |json_str| write!(f, "{json_str}")) } } \ No newline at end of file diff --git a/src/processing/playlist_watch.rs b/src/processing/playlist_watch.rs index 082bddc4f..0ea178a82 100644 --- a/src/processing/playlist_watch.rs +++ b/src/processing/playlist_watch.rs @@ -19,7 +19,7 @@ pub fn process_group_watch(cfg: &Config, target_name: &str, pl: &PlaylistGroup) let watch_filename = sanitize_filename(&format!("{file_name}.bin")); match file_utils::get_file_path(&cfg.working_dir, Some(std::path::PathBuf::from(&watch_filename))) { Some(path) => { - let save_path = path.clone(); + let save_path = path.as_path(); let mut changed = false; if path.exists() { if let Some(loaded_tree) = load_watch_tree(&path) { @@ -38,10 +38,10 @@ pub fn process_group_watch(cfg: &Config, target_name: &str, pl: &PlaylistGroup) changed = true; } if changed { - match save_watch_tree(&save_path, &new_tree) { + match save_watch_tree(save_path, &new_tree) { Ok(()) => {} Err(err) => { - error!("failed to write watch_file {}: {}", &save_path.to_str().unwrap_or_default(), err); + error!("failed to write watch_file {}: {}", save_path.to_str().unwrap_or_default(), err); } } } diff --git a/src/utils/download.rs b/src/utils/download.rs index 907eb7a77..fa1cc3b87 100644 --- a/src/utils/download.rs +++ b/src/utils/download.rs @@ -93,7 +93,8 @@ fn get_skip_cluster(input: &ConfigInput) -> Vec { } } if skip_cluster.len() == 3 { - info!("You have skipped all sections from xtream input {}", input.name.as_ref().unwrap_or(&input.id.to_string())); + let name = input.name.as_ref().map_or_else(|| input.id.to_string(), std::string::ToString::to_string); + info!("You have skipped all sections from xtream input {name}"); } skip_cluster } diff --git a/src/utils/file_lock_manager.rs b/src/utils/file_lock_manager.rs index 273234598..1f63028f2 100644 --- a/src/utils/file_lock_manager.rs +++ b/src/utils/file_lock_manager.rs @@ -60,11 +60,8 @@ impl Default for FileLockManager { impl fmt::Debug for FileLockManager { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Acquire the lock to safely access the HashMap - let locks = self.locks.lock().unwrap(); - let keys: Vec<_> = locks.keys().collect(); f.debug_struct("FileLockManager") - .field("locks", &keys) + .field("locks", &self.locks.lock().unwrap().keys().collect::>()) .finish() } } diff --git a/src/utils/request_utils.rs b/src/utils/request_utils.rs index 749e624c4..533c19610 100644 --- a/src/utils/request_utils.rs +++ b/src/utils/request_utils.rs @@ -7,9 +7,9 @@ use std::time::Instant; use flate2::read::{GzDecoder, ZlibDecoder}; use futures::StreamExt; -use log::{debug, error, Level, log_enabled}; -use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use log::{debug, error, log_enabled, Level}; use reqwest::header::CONTENT_ENCODING; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use url::Url; use crate::create_m3u_filter_error_result; @@ -18,7 +18,7 @@ use crate::model::config::ConfigInput; use crate::model::stats::format_elapsed_time; use crate::repository::storage::get_input_storage_path; use crate::repository::xtream_repository::FILE_EPG; -use crate::utils::compression_utils::{ENCODING_DEFLATE, ENCODING_GZIP, is_deflate, is_gzip}; +use crate::utils::compression_utils::{is_deflate, is_gzip, ENCODING_DEFLATE, ENCODING_GZIP}; use crate::utils::file_utils::{get_file_path, persist_file}; pub const fn bytes_to_megabytes(bytes: u64) -> u64 { @@ -123,18 +123,19 @@ pub async fn get_input_text_content(input: &ConfigInput, working_dir: &String, u } pub fn get_client_request(input: Option<&ConfigInput>, url: &Url, custom_headers: Option<&HashMap<&str, &[u8]>>) -> reqwest::RequestBuilder { - let mut request = reqwest::Client::new().get(url.clone()); - let headers = get_request_headers(input.map_or(&HashMap::new(), |i| &i.headers), custom_headers); - request = request.headers(headers); - request + let request = reqwest::Client::new().get(url.clone()); + let headers = get_request_headers(input.map(|i| &i.headers), custom_headers); + request.headers(headers) } -pub fn get_request_headers(defined_headers: &HashMap, custom_headers: Option<&HashMap<&str, &[u8]>>) -> HeaderMap { +pub fn get_request_headers(defined_headers: Option<&HashMap>, custom_headers: Option<&HashMap<&str, &[u8]>>) -> HeaderMap { let mut headers = HeaderMap::new(); - for (key, value) in defined_headers { - headers.insert( - HeaderName::from_bytes(key.as_bytes()).unwrap(), - HeaderValue::from_bytes(value.as_bytes()).unwrap()); + if let Some(def_headers) = defined_headers { + for (key, value) in def_headers { + headers.insert( + HeaderName::from_bytes(key.as_bytes()).unwrap(), + HeaderValue::from_bytes(value.as_bytes()).unwrap()); + } } if let Some(custom) = custom_headers { let header_keys: HashSet = headers.keys().map(|k| k.as_str().to_lowercase()).collect(); @@ -254,7 +255,7 @@ async fn get_remote_content(input: &ConfigInput, url: &Url) -> Result { debug!("Request took:{} {url}", format_elapsed_time(start_time.elapsed().as_secs())); Ok(decoded_content) - }, + } Err(err) => Err(std::io::Error::new(ErrorKind::Other, format!("failed to plain text content {err}"))) } } else { @@ -276,12 +277,12 @@ pub async fn download_text_content_as_file(input: &ConfigInput, url_str: &str, w if let Ok(url) = url_str.parse::() { if url.scheme() == "file" { url.to_file_path().map_or_else(|()| Err(Error::new(ErrorKind::Unsupported, format!("Unknown file {url}"))), |file_path| if file_path.exists() { - Ok(file_path) - } else { - Err(Error::new(ErrorKind::NotFound, format!("Unknown file {file_path:?}"))) - }) + Ok(file_path) + } else { + Err(Error::new(ErrorKind::NotFound, format!("Unknown file {file_path:?}"))) + }) } else { - let file_path = persist_filepath.map_or_else(|| match get_input_storage_path(input, working_dir) { + let file_path = persist_filepath.map_or_else(|| match get_input_storage_path(input, working_dir) { Ok(download_path) => { Ok(download_path.join(FILE_EPG)) }