From f02e8a11a25450d6d9d654948613f4df7fb134c2 Mon Sep 17 00:00:00 2001 From: euzu <33094714+euzu@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:32:46 +0100 Subject: [PATCH] Fixed info content type mismatch (#529) * Fixed info content type mismatch --- Cargo.lock | 6 +- backend/Cargo.toml | 2 +- .../src/processing/processor/xtream_series.rs | 1 + .../src/processing/processor/xtream_vod.rs | 1 + frontend/Cargo.toml | 4 +- .../playlist/_playlist_explorer.scss | 3 + shared/Cargo.toml | 2 +- shared/src/model/playlist.rs | 36 +++---- shared/src/model/playlist_document.rs | 97 ++++++++++--------- shared/src/model/playlist_info_document.rs | 64 ++++++------ shared/src/model/stream_properties.rs | 87 +++++++++-------- shared/src/utils/string_interner.rs | 66 ++++--------- 12 files changed, 178 insertions(+), 191 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e8fc258b..311bc7311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1156,7 +1156,7 @@ dependencies = [ [[package]] name = "frontend" -version = "3.2.46" +version = "3.2.47" dependencies = [ "anyhow", "base64", @@ -3994,7 +3994,7 @@ dependencies = [ [[package]] name = "shared" -version = "3.2.46" +version = "3.2.47" dependencies = [ "base64", "bitflags 2.10.0", @@ -4569,7 +4569,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tuliprox" -version = "3.2.46" +version = "3.2.47" dependencies = [ "arc-swap", "async-compression", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b973e2837..67473354b 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tuliprox" -version = "3.2.46" +version = "3.2.47" edition = "2021" rust-version = "1.89.0" diff --git a/backend/src/processing/processor/xtream_series.rs b/backend/src/processing/processor/xtream_series.rs index 6f51c1782..bd8540b91 100644 --- a/backend/src/processing/processor/xtream_series.rs +++ b/backend/src/processing/processor/xtream_series.rs @@ -77,6 +77,7 @@ async fn playlist_resolve_series_info(app_config: &Arc, client: &reqw .await { if !content.is_empty() { + //tokio::fs::write(&storage_path.join(format!("{provider_id}_series_info.json")), &content).await.ok(); match serde_json::from_str::(&content) { Ok(info) => { let series_stream_props = SeriesStreamProperties::from_info(&info, pli); diff --git a/backend/src/processing/processor/xtream_vod.rs b/backend/src/processing/processor/xtream_vod.rs index ac7b1aab2..aaffefb6d 100644 --- a/backend/src/processing/processor/xtream_vod.rs +++ b/backend/src/processing/processor/xtream_vod.rs @@ -67,6 +67,7 @@ pub async fn playlist_resolve_vod(app_config: &Arc, .await { if content.is_empty() { continue; } + //tokio::fs::write(&storage_path.join(format!("{provider_id}_vod_info.json")), &content).await.ok(); match serde_json::from_str::(&content) { Ok(info) => { let video_stream_props = VideoStreamProperties::from_info(&info, pli); diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index ba5a08fc1..56849d1f4 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "frontend" -version = "3.2.46" +version = "3.2.47" edition = "2021" rust-version = "1.89.0" [dependencies] -shared = { version = "3.2.46", path = "../shared" } +shared = { version = "3.2.47", path = "../shared" } chrono = "0" yew = "0.21" yew-router = "0.18" diff --git a/frontend/scss/app/components/playlist/_playlist_explorer.scss b/frontend/scss/app/components/playlist/_playlist_explorer.scss index 345b39f85..1ad480e64 100644 --- a/frontend/scss/app/components/playlist/_playlist_explorer.scss +++ b/frontend/scss/app/components/playlist/_playlist_explorer.scss @@ -248,6 +248,9 @@ $series-backdrop-height: 300px; &-logo { box-sizing: border-box; height: 3rem; + width: 3rem; + border-radius: var(--border-radius); + overflow: hidden; object-fit: cover; object-position: center; } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index d9a98a0e4..898bd93c9 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shared" -version = "3.2.46" +version = "3.2.47" edition = "2021" rust-version = "1.89.0" diff --git a/shared/src/model/playlist.rs b/shared/src/model/playlist.rs index 174676f0c..1c603f46e 100644 --- a/shared/src/model/playlist.rs +++ b/shared/src/model/playlist.rs @@ -1,4 +1,4 @@ -use crate::utils::{arc_str_option_serde, arc_str_serde, arc_str_serde_none, extract_extension_from_url, generate_playlist_uuid, +use crate::utils::{arc_str_option_serde, arc_str_serde, extract_extension_from_url, generate_playlist_uuid, get_provider_id, Internable}; use crate::model::{xtream_const, ClusterFlags, CommonPlaylistItem, ConfigTargetOptions, EpisodeStreamProperties, SeriesStreamProperties, StreamProperties, VideoStreamProperties, XtreamInfoDocument}; @@ -230,19 +230,19 @@ pub trait PlaylistEntry: Send + Sync { pub struct PlaylistItemHeader { #[serde(skip)] pub uuid: UUIDType, // calculated - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub id: Arc, // provider id pub virtual_id: VirtualId, // virtual id - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub name: Arc, pub chno: u32, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub logo: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub logo_small: Arc, #[serde(with = "arc_str_serde")] pub group: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub title: Arc, #[serde(with = "arc_str_serde")] pub parent_code: Arc, @@ -252,7 +252,7 @@ pub struct PlaylistItemHeader { pub time_shift: Arc, #[serde(with = "arc_str_serde")] pub rec: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub url: Arc, #[serde(default, with = "arc_str_option_serde")] pub epg_channel_id: Option>, @@ -420,18 +420,18 @@ generate_field_accessor_impl_for_playlist_item_header!(id, /*virtual_id,*/ title #[derive(Debug, Clone, Serialize, Deserialize)] pub struct M3uPlaylistItem { pub virtual_id: VirtualId, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub provider_id: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub name: Arc, pub chno: u32, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub logo: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub logo_small: Arc, #[serde(with = "arc_str_serde")] pub group: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub title: Arc, #[serde(with = "arc_str_serde")] pub parent_code: Arc, @@ -441,7 +441,7 @@ pub struct M3uPlaylistItem { pub time_shift: Arc, #[serde(with = "arc_str_serde")] pub rec: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub url: Arc, #[serde(default, with = "arc_str_option_serde")] pub epg_channel_id: Option>, @@ -648,21 +648,21 @@ impl XtreamMappingOptions { pub struct XtreamPlaylistItem { pub virtual_id: VirtualId, pub provider_id: u32, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub name: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub logo: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub logo_small: Arc, #[serde(with = "arc_str_serde")] pub group: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub title: Arc, #[serde(with = "arc_str_serde")] pub parent_code: Arc, #[serde(with = "arc_str_serde")] pub rec: Arc, - #[serde(with = "arc_str_serde_none")] + #[serde(with = "arc_str_serde")] pub url: Arc, #[serde(default, with = "arc_str_option_serde")] pub epg_channel_id: Option>, diff --git a/shared/src/model/playlist_document.rs b/shared/src/model/playlist_document.rs index 45d12c7df..1e24e9876 100644 --- a/shared/src/model/playlist_document.rs +++ b/shared/src/model/playlist_document.rs @@ -127,49 +127,50 @@ impl XtreamPlaylistItem { fn to_info_document_no_props(&self, resource_url: Option) -> XtreamInfoDocument { let stream_icon = self.get_stream_icon(resource_url); + let empty_str = "".intern(); match self.xtream_cluster { XtreamCluster::Live => XtreamInfoDocument::Empty(XtreamEmptyDoc {}), XtreamCluster::Video => { XtreamInfoDocument::Video(XtreamVideoInfoDoc { info: XtreamVideoInfoData { - kinopoisk_url: "".intern(), - tmdb_id: "".intern(), + kinopoisk_url: Arc::clone(&empty_str), + tmdb_id: Arc::clone(&empty_str), name: Arc::clone(&self.title), o_name: Arc::clone(&self.name), cover_big: Arc::clone(&stream_icon), movie_image: Arc::clone(&stream_icon), - release_date: "".intern(), + release_date: Arc::clone(&empty_str), episode_run_time: 0, - youtube_trailer: "".intern(), - director: "".intern(), - actors: "".intern(), - cast: "".intern(), - description: "".intern(), - plot: "".intern(), - age: "".intern(), - mpaa_rating: "".intern(), + youtube_trailer: Arc::clone(&empty_str), + director: Arc::clone(&empty_str), + actors: Arc::clone(&empty_str), + cast: Arc::clone(&empty_str), + description: Arc::clone(&empty_str), + plot: Arc::clone(&empty_str), + age: Arc::clone(&empty_str), + mpaa_rating: Arc::clone(&empty_str), rating_count_kinopoisk: 0, - country: "".intern(), - genre: "".intern(), + country: Arc::clone(&empty_str), + genre: Arc::clone(&empty_str), backdrop_path: vec![Arc::clone(&stream_icon)], - duration_secs: "".intern(), - duration: "".intern(), + duration_secs: "0".intern(), + duration: Arc::clone(&empty_str), video: Value::Array(Vec::new()), audio: Value::Array(Vec::new()), bitrate: 0, - rating: "".intern(), - runtime: "".intern(), + rating: Arc::clone(&empty_str), + runtime: Arc::clone(&empty_str), status: "Released".intern(), }, movie_data: XtreamVideoMovieData { stream_id: self.virtual_id, name: Arc::clone(&self.name), - added: "".intern(), + added: Arc::clone(&empty_str), category_id: self.category_id.intern(), category_ids: vec![self.category_id], - container_extension: "".intern(), + container_extension: Arc::clone(&empty_str), custom_sid: None, - direct_source: "".intern(), + direct_source: Arc::clone(&empty_str), }, }) } @@ -179,19 +180,19 @@ impl XtreamPlaylistItem { info: XtreamSeriesInfoData { name: Arc::clone(&self.title), cover: Arc::clone(&stream_icon), - plot: "".intern(), - cast: "".intern(), - director: "".intern(), - genre: "".intern(), - release_date_alternate: "".intern(), - release_date: "".intern(), - last_modified: "".intern(), - rating: "".intern(), - rating_5based: "".intern(), + plot: Arc::clone(&empty_str), + cast: Arc::clone(&empty_str), + director: Arc::clone(&empty_str), + genre: Arc::clone(&empty_str), + release_date_alternate: Arc::clone(&empty_str), + release_date: Arc::clone(&empty_str), + last_modified: Arc::clone(&empty_str), + rating: Arc::clone(&empty_str), + rating_5based: Arc::clone(&empty_str), backdrop_path: if stream_icon.is_empty() {vec![] } else { vec![Arc::clone(&stream_icon)] }, - tmdb: "".intern(), - youtube_trailer: "".intern(), - episode_run_time: "".intern(), + tmdb: Arc::clone(&empty_str), + youtube_trailer: Arc::clone(&empty_str), + episode_run_time: empty_str, category_id: self.category_id.intern(), category_ids: vec![self.category_id], }, @@ -217,19 +218,19 @@ impl XtreamPlaylistItem { fn series_to_document(&self, options: &XtreamMappingOptions, series: &SeriesStreamProperties) -> XtreamDocument { let resource_url = options.get_resource_url(self.xtream_cluster, self.item_type, self.virtual_id); - + let empty_str = "".intern(); XtreamDocument::Series(XtreamSeriesDoc { num: self.channel_no, name: self.title.clone(), series_id: self.virtual_id, cover: InfoDocUtils::make_resource_url(resource_url.as_deref(), &series.cover, "cover").intern(), - plot: series.plot.clone().unwrap_or_else(|| "".intern()), + plot: series.plot.clone().unwrap_or_else(|| Arc::clone(&empty_str)), cast: series.cast.clone(), director: series.director.clone(), - genre: series.genre.clone().unwrap_or_else(|| "".intern()), - release_date: series.release_date.clone().unwrap_or_else(|| "".intern()), - release_date_alternate: series.release_date.clone().unwrap_or_else(|| "".intern()), - last_modified: series.last_modified.clone().unwrap_or_else(|| "".intern()), + genre: series.genre.clone().unwrap_or_else(|| Arc::clone(&empty_str)), + release_date: series.release_date.clone().unwrap_or_else(|| Arc::clone(&empty_str)), + release_date_alternate: series.release_date.clone().unwrap_or_else(|| Arc::clone(&empty_str)), + last_modified: series.last_modified.clone().unwrap_or_else(|| Arc::clone(&empty_str)), rating: InfoDocUtils::limited(series.rating).intern(), rating_5based: InfoDocUtils::limited(series.rating_5based).intern(), backdrop_path: series.backdrop_path.as_ref().map_or_else( @@ -241,8 +242,8 @@ impl XtreamPlaylistItem { InfoDocUtils::make_bdpath_resource_url(resource_url.as_deref(), p, idx, "").intern() ).collect()), youtube_trailer: series.youtube_trailer.clone(), - tmdb: series.tmdb.map(|v| v.intern()).unwrap_or_else(|| "".intern()), - episode_runtime: series.episode_run_time.clone().unwrap_or_else(|| "".intern()), + tmdb: series.tmdb.map(|v| v.intern()).unwrap_or_else(|| Arc::clone(&empty_str)), + episode_runtime: series.episode_run_time.clone().unwrap_or(empty_str), category_id: self.category_id.intern(), category_ids: vec![self.category_id], }) @@ -251,43 +252,45 @@ impl XtreamPlaylistItem { fn video_to_document(&self, options: &XtreamMappingOptions, video: &VideoStreamProperties) -> XtreamDocument { let resource_url = options.get_resource_url(self.xtream_cluster, self.item_type, self.virtual_id); let stream_icon = self.get_stream_icon(resource_url); + let empty_str = "".intern(); XtreamDocument::Video(XtreamVideoDoc { num: self.channel_no, name: self.title.clone(), stream_type: video.stream_type.clone().unwrap_or_else(default_as_movie), stream_id: self.virtual_id, stream_icon, - rating: video.rating.map(|v| InfoDocUtils::limited(v).intern()).unwrap_or_else(|| "".intern()), + rating: video.rating.map(|v| InfoDocUtils::limited(v).intern()).unwrap_or_else(|| Arc::clone(&empty_str)), rating_5based: video.rating_5based.unwrap_or_default(), - tmdb: video.tmdb.map(|v| v.intern()).unwrap_or_else(|| "".intern()), - trailer: video.trailer.clone().unwrap_or_else(|| "".intern()), + tmdb: video.tmdb.map(|v| v.intern()).unwrap_or_else(|| Arc::clone(&empty_str)), + trailer: video.trailer.clone().unwrap_or_else(|| Arc::clone(&empty_str)), added: video.added.clone(), is_adult: video.is_adult, category_id: self.category_id.intern(), category_ids: vec![self.category_id], container_extension: video.container_extension.clone(), custom_sid: video.custom_sid.clone(), - direct_source: if options.skip_video_direct_source { "".intern() } else { video.direct_source.clone() }, + direct_source: if options.skip_video_direct_source { empty_str } else { video.direct_source.clone() }, }) } fn live_to_document(&self, options: &XtreamMappingOptions, live: &LiveStreamProperties) -> XtreamDocument { let resource_url = options.get_resource_url(self.xtream_cluster, self.item_type, self.virtual_id); let stream_icon = self.get_stream_icon(resource_url); + let empty_str = "".intern(); XtreamDocument::Live(XtreamLiveDoc { num: self.channel_no, name: self.title.clone(), stream_type: live.stream_type.clone().unwrap_or_else(default_as_live), stream_id: self.virtual_id, stream_icon, - epg_channel_id: self.epg_channel_id.clone().unwrap_or_else(|| "".intern()), - added: live.added.clone().unwrap_or_else(|| "".intern()), + epg_channel_id: self.epg_channel_id.clone().unwrap_or_else(|| Arc::clone(&empty_str)), + added: live.added.clone().unwrap_or_else(|| Arc::clone(&empty_str)), is_adult: live.is_adult, category_id: self.category_id.intern(), category_ids: vec![self.category_id], custom_sid: live.custom_sid.clone(), tv_archive: live.tv_archive.unwrap_or_default(), - direct_source: if options.skip_live_direct_source { "".intern() } else { live.direct_source.clone() }, + direct_source: if options.skip_live_direct_source { empty_str } else { live.direct_source.clone() }, tv_archive_duration: live.tv_archive_duration.unwrap_or_default(), }) } diff --git a/shared/src/model/playlist_info_document.rs b/shared/src/model/playlist_info_document.rs index 0c06a3e16..29af94dd3 100644 --- a/shared/src/model/playlist_info_document.rs +++ b/shared/src/model/playlist_info_document.rs @@ -287,6 +287,7 @@ impl StreamProperties { let resource_url = options.get_resource_url(XtreamCluster::Video, item_type, virtual_id); let stream_icon = InfoDocUtils::make_resource_url(resource_url.as_deref(), &self.get_stream_icon(), "logo").intern(); let empty_str = "".intern(); + let zero_str = "0".intern(); let info = if let Some(details) = video.details.as_ref() { XtreamVideoInfoData { @@ -312,7 +313,7 @@ impl StreamProperties { backdrop_path: details.backdrop_path.as_deref().map_or_else(Vec::new, |b| b.iter().enumerate().map(|(idx, p)| InfoDocUtils::make_bdpath_resource_url(resource_url.as_deref(), p, idx, "nfo_").intern() ).collect()), - duration_secs: details.duration_secs.as_ref().map(Arc::clone).unwrap_or_else(|| Arc::clone(&empty_str)), + duration_secs: details.duration_secs.as_ref().map_or_else(|| Arc::clone(&empty_str), Arc::clone), duration: details.duration.as_ref().map(Arc::clone).unwrap_or_else(|| Arc::clone(&empty_str)), video: InfoDocUtils::build_value(details.video.as_ref().map(Arc::as_ref)), audio: InfoDocUtils::build_value(details.audio.as_ref().map(Arc::as_ref)), @@ -343,13 +344,13 @@ impl StreamProperties { country: Arc::clone(&empty_str), genre: Arc::clone(&empty_str), backdrop_path: vec![Arc::clone(&stream_icon)], - duration_secs: "0".intern(), - duration: "0".intern(), + duration_secs: Arc::clone(&zero_str), + duration: Arc::clone(&zero_str), video: Value::Array(Vec::new()), audio: Value::Array(Vec::new()), bitrate: 0, rating: InfoDocUtils::limited(video.rating.unwrap_or_default()).intern(), - runtime: "0".intern(), + runtime: zero_str, status: "Released".intern(), } }; @@ -432,46 +433,47 @@ impl StreamProperties { impl Default for XtreamVideoInfoDoc { fn default() -> Self { + let empty_str = "".intern(); Self { info: XtreamVideoInfoData { - kinopoisk_url: "".intern(), - tmdb_id: "".intern(), - name: "".intern(), - o_name: "".intern(), - cover_big: "".intern(), - movie_image: "".intern(), - release_date: "".intern(), + kinopoisk_url: Arc::clone(&empty_str), + tmdb_id: Arc::clone(&empty_str), + name: Arc::clone(&empty_str), + o_name: Arc::clone(&empty_str), + cover_big: Arc::clone(&empty_str), + movie_image: Arc::clone(&empty_str), + release_date: Arc::clone(&empty_str), episode_run_time: 0, - youtube_trailer: "".intern(), - director: "".intern(), - actors: "".intern(), - cast: "".intern(), - description: "".intern(), - plot: "".intern(), - age: "".intern(), - mpaa_rating: "".intern(), + youtube_trailer: Arc::clone(&empty_str), + director: Arc::clone(&empty_str), + actors: Arc::clone(&empty_str), + cast: Arc::clone(&empty_str), + description: Arc::clone(&empty_str), + plot: Arc::clone(&empty_str), + age: Arc::clone(&empty_str), + mpaa_rating: Arc::clone(&empty_str), rating_count_kinopoisk: 0, - country: "".intern(), - genre: "".intern(), + country: Arc::clone(&empty_str), + genre: Arc::clone(&empty_str), backdrop_path: vec![], - duration_secs: "".intern(), - duration: "".intern(), + duration_secs: "0".intern(), + duration: Arc::clone(&empty_str), video: Value::Array(Vec::new()), audio: Value::Array(Vec::new()), bitrate: 0, - rating: "".intern(), - runtime: "".intern(), - status: "".intern(), + rating: Arc::clone(&empty_str), + runtime: Arc::clone(&empty_str), + status: Arc::clone(&empty_str), }, movie_data: XtreamVideoMovieData { stream_id: 0, - name: "".intern(), - added: "".intern(), - category_id: "".intern(), + name: Arc::clone(&empty_str), + added: Arc::clone(&empty_str), + category_id: Arc::clone(&empty_str), category_ids: vec![], - container_extension: "".intern(), + container_extension: Arc::clone(&empty_str), custom_sid: None, - direct_source: "".intern(), + direct_source: empty_str, }, } } diff --git a/shared/src/model/stream_properties.rs b/shared/src/model/stream_properties.rs index 50f365fe6..2eb22141b 100644 --- a/shared/src/model/stream_properties.rs +++ b/shared/src/model/stream_properties.rs @@ -1,9 +1,10 @@ use crate::model::info_doc_utils::InfoDocUtils; use crate::model::{PlaylistEntry, XtreamSeriesInfo, XtreamSeriesInfoDoc, XtreamVideoInfo}; -use crate::utils::{arc_str_default_on_null, arc_str_none_default_on_null, arc_str_option_serde_none, - deserialize_as_option_arc_str, deserialize_as_option_arc_str_none, - deserialize_as_string_array, deserialize_json_as_opt_string, deserialize_number_from_string, - deserialize_number_from_string_or_zero, serialize_json_as_opt_string, serialize_option_string_as_null_if_empty, Internable}; +use crate::utils::{arc_str_default_on_null, arc_str_none_default_on_null, arc_str_option_serde, + deserialize_as_option_arc_str, deserialize_as_string_array, + deserialize_json_as_opt_string, deserialize_number_from_string, + deserialize_number_from_string_or_zero, serialize_json_as_opt_string, + serialize_option_string_as_null_if_empty, Internable}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use log::{warn}; @@ -20,9 +21,9 @@ pub struct LiveStreamProperties { pub stream_icon: Arc, #[serde(default, deserialize_with = "arc_str_none_default_on_null")] pub direct_source: Arc, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none", serialize_with = "serialize_option_string_as_null_if_empty")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str", serialize_with = "serialize_option_string_as_null_if_empty")] pub custom_sid: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub added: Option>, #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub stream_type: Option>, @@ -38,45 +39,45 @@ pub struct LiveStreamProperties { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct VideoStreamDetailProperties { - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub kinopoisk_url: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub o_name: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub cover_big: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub movie_image: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub release_date: Option>, #[serde(default, deserialize_with = "deserialize_number_from_string")] pub episode_run_time: Option, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub youtube_trailer: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub director: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub actors: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub cast: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub description: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub plot: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub age: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub mpaa_rating: Option>, #[serde(default)] pub rating_count_kinopoisk: u32, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub country: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub genre: Option>, #[serde(default, deserialize_with = "deserialize_as_string_array")] pub backdrop_path: Option>>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub duration_secs: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub duration: Option>, #[serde( default, @@ -92,9 +93,9 @@ pub struct VideoStreamDetailProperties { pub audio: Option>, #[serde(default)] pub bitrate: u32, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub runtime: Option>, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub status: Option>, } @@ -110,7 +111,7 @@ pub struct VideoStreamProperties { pub stream_icon: Arc, #[serde(default, deserialize_with = "arc_str_none_default_on_null")] pub direct_source: Arc, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none", serialize_with = "serialize_option_string_as_null_if_empty")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str", serialize_with = "serialize_option_string_as_null_if_empty")] pub custom_sid: Option>, #[serde(default, deserialize_with = "arc_str_none_default_on_null")] pub added: Arc, @@ -122,7 +123,7 @@ pub struct VideoStreamProperties { pub rating_5based: Option, #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub stream_type: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub trailer: Option>, #[serde(default, deserialize_with = "deserialize_number_from_string")] pub tmdb: Option, @@ -140,17 +141,17 @@ pub struct SeriesStreamDetailSeasonProperties { pub season_number: u32, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub episode_count: u32, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub overview: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub air_date: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub cover: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub cover_tmdb: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub cover_big: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub duration: Option>, } @@ -166,7 +167,7 @@ pub struct SeriesStreamDetailEpisodeProperties { pub title: Arc, #[serde(default, deserialize_with = "arc_str_default_on_null")] pub container_extension: Arc, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none", serialize_with = "serialize_option_string_as_null_if_empty")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str", serialize_with = "serialize_option_string_as_null_if_empty")] pub custom_sid: Option>, #[serde(default, deserialize_with = "arc_str_none_default_on_null")] pub added: Arc, @@ -176,9 +177,9 @@ pub struct SeriesStreamDetailEpisodeProperties { pub tmdb: Option, #[serde(default, deserialize_with = "arc_str_none_default_on_null")] pub release_date: Arc, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub plot: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub crew: Option>, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub duration_secs: u32, @@ -230,19 +231,19 @@ pub struct SeriesStreamProperties { pub cover: Arc, #[serde(default, deserialize_with = "arc_str_none_default_on_null")] pub director: Arc, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub episode_run_time: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub genre: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub last_modified: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub plot: Option>, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub rating: f64, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub rating_5based: f64, - #[serde(default, with = "arc_str_option_serde_none")] + #[serde(default, with = "arc_str_option_serde")] pub release_date: Option>, #[serde(default, deserialize_with = "arc_str_none_default_on_null")] pub youtube_trailer: Arc, @@ -260,9 +261,9 @@ pub struct EpisodeStreamProperties { pub episode: u32, #[serde(default, deserialize_with = "deserialize_number_from_string_or_zero")] pub season: u32, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub added: Option>, - #[serde(default, deserialize_with = "deserialize_as_option_arc_str_none")] + #[serde(default, deserialize_with = "deserialize_as_option_arc_str")] pub release_date: Option>, #[serde(default, deserialize_with = "deserialize_number_from_string")] pub tmdb: Option, diff --git a/shared/src/utils/string_interner.rs b/shared/src/utils/string_interner.rs index 7b353d92b..ec4bbc5a8 100644 --- a/shared/src/utils/string_interner.rs +++ b/shared/src/utils/string_interner.rs @@ -138,24 +138,6 @@ pub fn interner_gc() -> usize { 0 } -pub mod arc_str_serde { - use super::*; - pub fn serialize(value: &Arc, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(value) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - Ok(s.intern()) - } -} - pub mod arc_str_vec_serde { use super::*; use serde::ser::SerializeSeq; @@ -185,8 +167,9 @@ pub mod arc_str_vec_serde { } } -pub mod arc_str_serde_none { +pub mod arc_str_serde { use super::*; + use serde_json::Value; pub fn serialize(value: &Arc, serializer: S) -> Result where S: Serializer, @@ -198,13 +181,20 @@ pub mod arc_str_serde_none { where D: Deserializer<'de>, { - let s = String::deserialize(deserializer)?; - Ok(s.intern()) + match Value::deserialize(deserializer)? { + Value::Null => Ok("".intern()), + Value::Bool(b) => Ok(b.to_string().intern()), + Value::Number(n) => Ok(n.to_string().intern()), + Value::String(v) => Ok(v.intern()), + Value::Array(_) => Ok("".intern()), + Value::Object(_) => Ok("".intern()), + } } } pub mod arc_str_option_serde { use super::*; + use serde_json::Value; pub fn serialize(value: &Option>, serializer: S) -> Result where S: Serializer, @@ -219,8 +209,16 @@ pub mod arc_str_option_serde { where D: Deserializer<'de>, { - let opt = Option::::deserialize(deserializer)?; - Ok(opt.map(|s| s.intern())) + let opt = Option::::deserialize(deserializer)?; + match opt { + Some(value) => match value { + Value::Bool(b) => Ok(Some(b.to_string().intern())), + Value::Number(n) => Ok(Some(n.to_string().intern())), + Value::String(v) => Ok(Some(v.intern())), + Value::Null | Value::Array(_) | Value::Object(_) => Ok(None), + }, + None => Ok(None) + } } pub fn serialize_null_if_empty(value: &Option>, serializer: S) -> Result @@ -235,26 +233,6 @@ pub mod arc_str_option_serde { } } -pub mod arc_str_option_serde_none { - use super::*; - pub fn serialize(value: &Option>, serializer: S) -> Result - where - S: Serializer, - { - match value { - Some(s) => serializer.serialize_str(s), - None => serializer.serialize_none(), - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - let opt = Option::::deserialize(deserializer)?; - Ok(opt.map(|s| s.intern())) - } -} pub fn arc_str_default_on_null<'de, D>(deserializer: D) -> Result, D::Error> where @@ -277,5 +255,3 @@ where _ => Ok(None), } } - -pub use deserialize_as_option_arc_str as deserialize_as_option_arc_str_none;