diff --git a/src/model/xtream.rs b/src/model/xtream.rs index 972d87a7a..58836aefe 100644 --- a/src/model/xtream.rs +++ b/src/model/xtream.rs @@ -2,10 +2,10 @@ use std::collections::HashMap; use std::iter::FromIterator; use std::rc::Rc; +use crate::utils::json_utils::{opt_string_or_number_u32, string_default_on_null, string_or_number_f64, string_or_number_u32}; use serde::de::DeserializeOwned; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::{Map, Value}; -use crate::utils::json_utils::{string_or_number_u32, string_or_number_f64, opt_string_or_number_u32}; use crate::model::config::ConfigTargetOptions; use crate::model::playlist::{PlaylistItem, XtreamCluster, XtreamPlaylistItem}; @@ -266,23 +266,23 @@ impl XtreamStream { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct XtreamSeriesInfoSeason { - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub air_date: String, #[serde(default, deserialize_with = "string_or_number_u32")] pub episode_count: u32, #[serde(default, deserialize_with = "string_or_number_u32")] pub id: u32, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub name: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub overview: String, #[serde(default, deserialize_with = "string_or_number_u32")] pub season_number: u32, #[serde(default, deserialize_with = "string_or_number_f64")] pub vote_average: f64, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub cover: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub cover_big: String, } @@ -290,31 +290,37 @@ pub struct XtreamSeriesInfoSeason { #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[allow(non_snake_case)] pub struct XtreamSeriesInfoInfo { - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub(crate) name: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] cover: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] plot: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] cast: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] director: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] genre: String, - #[serde(default, alias = "release_date", alias = "releaseDate", alias = "releasedate")] + #[serde( + default, + alias = "release_date", + alias = "releaseDate", + alias = "releasedate", + deserialize_with = "string_default_on_null" + )] release_date: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] last_modified: String, #[serde(default, deserialize_with = "string_or_number_f64")] rating: f64, #[serde(default, deserialize_with = "string_or_number_f64")] rating_5based: f64, - #[serde(default)] - backdrop_path: Vec, - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_as_string_array")] + pub backdrop_path: Option>, + #[serde(default, deserialize_with = "string_default_on_null")] youtube_trailer: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] episode_run_time: String, #[serde(default, deserialize_with = "string_or_number_u32")] category_id: u32, @@ -324,15 +330,21 @@ pub struct XtreamSeriesInfoInfo { pub struct XtreamSeriesInfoEpisodeInfo { #[serde(default, deserialize_with = "opt_string_or_number_u32")] pub tmdb_id: Option, - #[serde(default, alias = "release_date", alias = "releaseDate", alias = "releasedate")] + #[serde( + default, + alias = "release_date", + alias = "releaseDate", + alias = "releasedate", + deserialize_with = "string_default_on_null" + )] pub releasedate: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub plot: String, #[serde(default, deserialize_with = "string_or_number_u32")] pub duration_secs: u32, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub duration: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub movie_image: String, #[serde(default)] pub video: Value, @@ -349,23 +361,23 @@ pub struct XtreamSeriesInfoEpisodeInfo { // Used for serde_json deserialization, can not be used with bincode #[derive(Debug, Clone, Serialize, Deserialize)] pub struct XtreamSeriesInfoEpisode { - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub id: String, #[serde(default, deserialize_with = "string_or_number_u32")] pub episode_num: u32, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub title: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub container_extension: String, #[serde(default)] pub info: XtreamSeriesInfoEpisodeInfo, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub custom_sid: String, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub added: String, #[serde(default, deserialize_with = "string_or_number_u32")] pub season: u32, - #[serde(default)] + #[serde(default, deserialize_with = "string_default_on_null")] pub direct_source: String, } @@ -420,8 +432,9 @@ impl XtreamSeriesInfoEpisode { pub fn get_additional_properties(&self, series_info: &XtreamSeriesInfo) -> Option { let mut result = Map::new(); let bdpath = &series_info.info.backdrop_path; - if !bdpath.is_empty() { - result.insert(String::from("backdrop_path"), Value::Array(Vec::from([Value::String(String::from(bdpath.first()?))]))); + let bdpath_is_set = bdpath.as_ref().map_or(false, |bdpath| !bdpath.is_empty()); + if bdpath_is_set { + result.insert(String::from("backdrop_path"), Value::Array(Vec::from([Value::String(String::from(bdpath.as_ref().unwrap().first()?))]))); } add_str_property_if_exists!(result, series_info.info.name.as_str(), "series_name"); add_str_property_if_exists!(result, series_info.info.release_date, "series_release_date"); diff --git a/src/processing/xtream_processor_series.rs b/src/processing/xtream_processor_series.rs index ba315a3af..6aac8ab19 100644 --- a/src/processing/xtream_processor_series.rs +++ b/src/processing/xtream_processor_series.rs @@ -74,7 +74,8 @@ async fn playlist_resolve_series_info(cfg: &Config, errors: &mut Vec 0 && elapsed % 5 == 0 { - info!("resolved {processed_series_info}/{series_info_count} series info"); + info!("resolved {processed_series_info_count}/{series_info_count} series info"); + last_processed_series_info_count = processed_series_info_count; } } - info!("resolved {processed_series_info}/{series_info_count} series info"); + if last_processed_series_info_count != processed_series_info_count { + info!("resolved {processed_series_info_count}/{series_info_count} series info"); + } // content_wal contains the provider_id and series_info with episode listing // record_wal contains provider_id and timestamp if content_updated { diff --git a/src/repository/epg_repository.rs b/src/repository/epg_repository.rs index 92cfeea92..f69329d62 100644 --- a/src/repository/epg_repository.rs +++ b/src/repository/epg_repository.rs @@ -41,9 +41,6 @@ pub fn epg_write(target: &ConfigTarget, cfg: &Config, target_path: &Path, epg: O if let Some(epg_data) = epg { match &output.target { TargetType::M3u => { - if output.filename.is_none() { - return Err(notify_err!(format!("write epg for target {} failed: No filename set", target.name))); - } let path = m3u_get_epg_file_path(target_path); debug_if_enabled!("writing m3u epg to {}", path.to_str().unwrap_or("?")); epg_write_file(target, epg_data, &path)?; diff --git a/src/repository/xtream_repository.rs b/src/repository/xtream_repository.rs index bd78e6e9f..2f5846ece 100644 --- a/src/repository/xtream_repository.rs +++ b/src/repository/xtream_repository.rs @@ -849,10 +849,7 @@ pub async fn xtream_update_input_series_episodes_record_from_wal_file( #[cfg(test)] mod tests { - use std::path::PathBuf; use std::io; - use crate::repository::bplustree::{BPlusTree}; - use crate::repository::target_id_mapping::VirtualIdRecord; #[test] fn test() -> io::Result<()> { diff --git a/src/utils/config_reader.rs b/src/utils/config_reader.rs index d6117a2df..4578d65be 100644 --- a/src/utils/config_reader.rs +++ b/src/utils/config_reader.rs @@ -146,4 +146,15 @@ pub fn resolve_env_var(value: &str) -> String { let var_name = &caps["var"]; env::var(var_name).unwrap_or_else(|_| format!("${{env:{var_name}}}")) }).to_string() +} + +#[cfg(test)] +mod tests { + use crate::utils::config_reader::resolve_env_var; + + #[test] + fn test_resolve() { + let resolved = resolve_env_var("${env:HOME}"); + assert_eq!(resolved, std::env::var("HOME").unwrap()); + } } \ No newline at end of file diff --git a/src/utils/json_utils.rs b/src/utils/json_utils.rs index 2810b6bf6..53fc472f9 100644 --- a/src/utils/json_utils.rs +++ b/src/utils/json_utils.rs @@ -4,7 +4,7 @@ use std::io::{self, BufReader, BufWriter, Error, Read, Write}; use std::path::Path; use serde::de::DeserializeOwned; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::{self, Deserializer, Value}; fn read_skipping_ws(mut reader: impl Read) -> io::Result { @@ -200,3 +200,11 @@ pub fn get_string_from_serde_value(value: &Value) -> Option { _ => None, } } + +pub fn string_default_on_null<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let value: Option = Option::deserialize(deserializer)?; + Ok(value.unwrap_or_else(String::new)) +} \ No newline at end of file