diff --git a/src/processing/playlist_processor.rs b/src/processing/playlist_processor.rs index d9c6af259..4a16433b8 100644 --- a/src/processing/playlist_processor.rs +++ b/src/processing/playlist_processor.rs @@ -530,14 +530,14 @@ pub(crate) async fn process_playlist<'a>(playlists: &mut [FetchedPlaylist<'a>], } } - persist_playlist(&new_playlist, flatten_tvguide(&new_epg), target, cfg) + persist_playlist(&mut new_playlist, flatten_tvguide(&new_epg), target, cfg) } else { info!("Playlist is empty: {}", &target.name); Ok(()) } } -fn persist_playlist(playlist: &[PlaylistGroup], epg: Option, +fn persist_playlist(playlist: &mut [PlaylistGroup], epg: Option, target: &ConfigTarget, cfg: &Config) -> Result<(), Vec> { let mut errors = vec![]; for output in &target.output { diff --git a/src/processing/xtream_parser.rs b/src/processing/xtream_parser.rs index d7cc96756..0979fa21a 100644 --- a/src/processing/xtream_parser.rs +++ b/src/processing/xtream_parser.rs @@ -141,7 +141,7 @@ pub(crate) fn parse_xtream(input: &ConfigInput, Ok(Some(group_map.values().map(|category| { let cat = category.borrow(); PlaylistGroup { - id: cat.category_id.parse::().unwrap_or(0), + id: 0, xtream_cluster: xtream_cluster.clone(), title: Rc::clone(&cat.category_name), channels: cat.channels.clone() diff --git a/src/repository/xtream_repository.rs b/src/repository/xtream_repository.rs index e43ad828b..ccb78b9fe 100644 --- a/src/repository/xtream_repository.rs +++ b/src/repository/xtream_repository.rs @@ -14,6 +14,7 @@ use crate::{create_m3u_filter_error_result}; use crate::api::api_model::AppState; use crate::m3u_filter_error::{M3uFilterError, M3uFilterErrorKind}; use crate::utils::file_utils; +use crate::utils::json_utils::iter_json_array; type IndexTree = BTreeMap; @@ -113,7 +114,7 @@ fn write_xtream_info(app_state: &AppState, target_name: &str, stream_id: i32, cl Ok(()) } -pub(crate) fn write_xtream_playlist(target: &ConfigTarget, cfg: &Config, playlist: &[PlaylistGroup]) -> Result<(), M3uFilterError> { +pub(crate) fn write_xtream_playlist(target: &ConfigTarget, cfg: &Config, playlist: &mut [PlaylistGroup]) -> Result<(), M3uFilterError> { if let Some(path) = get_xtream_storage_path(cfg, &target.name) { if fs::create_dir_all(&path).is_err() { let msg = format!("Failed to save, can't create directory {}", &path.to_str().unwrap()); @@ -135,15 +136,25 @@ pub(crate) fn write_xtream_playlist(target: &ConfigTarget, cfg: &Config, playlis let mut channel_num: i32 = 0; let mut errors = Vec::new(); - for plg in playlist { + + // preserve category_ids + let (max_cat_id, existing_cat_ids) = load_old_category_ids(&path); + let mut cat_id_counter = max_cat_id; + for plg in playlist.iter_mut() { if !&plg.channels.is_empty() { + let cat_id = existing_cat_ids.get(plg.title.as_ref()).unwrap_or_else(|| { + cat_id_counter += 1; + &cat_id_counter + }); + plg.id = *cat_id; + match &plg.xtream_cluster { XtreamCluster::Live => &mut cat_live_col, XtreamCluster::Series => &mut cat_series_col, XtreamCluster::Video => &mut cat_vod_col, }.push( json!({ - "category_id": format!("{}", &plg.id), + "category_id": format!("{}", &cat_id), "category_name": plg.title.clone(), "parent_id": 0 })); @@ -157,8 +168,8 @@ pub(crate) fn write_xtream_playlist(target: &ConfigTarget, cfg: &Config, playlis } channel_num += 1; let mut document = serde_json::Map::from_iter([ - ("category_id".to_string(), Value::String(format!("{}", &plg.id))), - ("category_ids".to_string(), Value::Array(Vec::from([Value::Number(serde_json::Number::from(plg.id.to_owned()))]))), + ("category_id".to_string(), Value::String(format!("{}", &cat_id))), + ("category_ids".to_string(), Value::Array(Vec::from([Value::Number(serde_json::Number::from(cat_id.to_owned()))]))), ("name".to_string(), Value::String(header.name.as_ref().clone())), ("num".to_string(), Value::Number(serde_json::Number::from(channel_num))), ("title".to_string(), Value::String(header.title.as_ref().clone())), @@ -261,6 +272,38 @@ pub(crate) fn write_xtream_playlist(target: &ConfigTarget, cfg: &Config, playlis Ok(()) } +fn load_old_category_ids(path: &Path) -> (u32, HashMap) { + let mut result: HashMap = HashMap::new(); + let mut max_id: u32 = 0; + for col_path in [ + get_collection_path(path, COL_CAT_LIVE), + get_collection_path(path, COL_CAT_VOD), + get_collection_path(path, COL_CAT_SERIES)] { + if col_path.exists() { + if let Ok(file) = File::open(col_path) { + let reader = BufReader::new(file); + for entry in iter_json_array::>(reader).flatten() { + if let Some(item) = entry.as_object() { + if let Some(category_id_value) = item.get("category_id") { + if let Some(category_id) = category_id_value.as_str() { + if let Some(category_name_value) = item.get("category_name") { + if let Some(category_name) = category_name_value.as_str() { + if let Ok(cat_id) = category_id.to_string().parse::() { + result.insert(category_name.to_string(), cat_id); + max_id = max_id.max(cat_id); + } + } + } + } + } + } + } + } + } + } + (max_id, result) +} + fn append_prepared_series_properties(header: &Ref, document: &mut Map) { if let Some(add_props) = &header.additional_properties { match add_props.iter().find(|(key, _)| key.eq("rating")) { diff --git a/src/utils/download.rs b/src/utils/download.rs index 65dd58aba..1731eee83 100644 --- a/src/utils/download.rs +++ b/src/utils/download.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::path::PathBuf; use std::thread::sleep; use log::{debug, info}; @@ -148,10 +149,15 @@ pub(crate) async fn get_xtream_playlist(input: &ConfigInput, working_dir: &Strin } } } + playlist.sort_by(|a, b| a.title.partial_cmp(&b.title).unwrap_or(Ordering::Greater)); + let mut counter = 1; + playlist.iter_mut().for_each(|plg| { + plg.id = counter; + counter += 1; + }); (playlist, errors) } - pub(crate) async fn get_xmltv(_cfg: &Config, input: &ConfigInput, working_dir: &String) -> (Option, Vec) { match &input.epg_url { None => (None, vec![]),