config added to shared model

This commit is contained in:
euzu
2025-06-19 16:37:37 +02:00
parent 068150561a
commit 7f54327bfd
92 changed files with 982 additions and 219 deletions
Generated
+1
View File
@@ -2581,6 +2581,7 @@ name = "shared"
version = "0.1.0"
dependencies = [
"bitflags 2.9.1",
"enum-iterator",
"rand",
"regex",
"serde",
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::api::model::app_state::AppState;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::utils;
use crate::utils::is_directory;
use log::{debug, error, info};
+1 -1
View File
@@ -13,7 +13,7 @@ use std::sync::Arc;
use std::{fs};
use axum::response::IntoResponse;
use shared::utils::bytes_to_megabytes;
use crate::tuliprox_error::to_io_error;
use shared::error::to_io_error;
use crate::utils::request::create_client;
async fn download_file(active: Arc<RwLock<Option<FileDownload>>>, client: &reqwest::Client) -> Result<(), String> {
+1 -1
View File
@@ -6,7 +6,7 @@ use crate::api::model::config::{ServerConfig, ServerInputConfig, ServerSourceCon
use crate::api::model::request::{PlaylistRequest, PlaylistRequestType};
use crate::auth::create_access_token;
use crate::auth::validator_admin;
use crate::tuliprox_error::TuliproxError;
use shared::error::TuliproxError;
use crate::model::StatusCheck;
use crate::model::XtreamPlaylistItem;
use crate::model::{Config, ConfigDto, ConfigInput, ConfigInputOptions, ConfigSource, ConfigTarget, InputType};
+3 -3
View File
@@ -16,9 +16,9 @@ use crate::model::{ProxyType, ProxyUserCredentials, UserConnectionPermission};
use crate::repository::playlist_repository::get_target_id_mapping;
use crate::repository::storage::{get_target_storage_path};
use crate::repository::{storage_const, user_repository, xtream_repository};
use crate::tuliprox_error::create_tuliprox_error_result;
use crate::tuliprox_error::info_err;
use crate::tuliprox_error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use shared::error::create_tuliprox_error_result;
use shared::error::info_err;
use shared::error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use crate::utils::{generate_playlist_uuid, hex_encode};
use crate::utils::get_u32_from_serde_value;
use crate::utils::request::{extract_extension_from_url, sanitize_sensitive_info};
+2 -2
View File
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::model::{ApiProxyConfig, ConfigApi, InputType, LogConfig, MessagingConfig, ProcessingOrder, ReverseProxyConfig, ScheduleConfig, VideoConfig, ConfigRename, ConfigSort, ConfigTargetOptions, TargetOutput, WebUiConfig, ProxyConfig, IpCheckConfig};
use shared::model::ProcessingOrder;
use crate::model::{ApiProxyConfig, ConfigApi, InputType, LogConfig, MessagingConfig, ReverseProxyConfig, ScheduleConfig, VideoConfig, ConfigRename, ConfigSort, ConfigTargetOptions, TargetOutput, WebUiConfig, ProxyConfig, IpCheckConfig};
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ServerInputConfig {
+1 -1
View File
@@ -5,7 +5,7 @@ use crate::api::api_utils::get_username_from_auth_header;
use crate::model::WebAuthConfig;
use crate::api::model::app_state::AppState;
use crate::auth::AuthBearer;
use crate::tuliprox_error::to_io_error;
use shared::error::to_io_error;
const ROLE_ADMIN: &str = "ADMIN";
const ROLE_USER: &str = "USER";
+1 -1
View File
@@ -1,6 +1,6 @@
use rand::{Rng};
use rand::distr::Alphanumeric;
use crate::tuliprox_error::str_to_io_error;
use shared::error::str_to_io_error;
fn generate_salt(length: usize) -> String {
let salt: String = rand::rng()
+4 -4
View File
@@ -8,11 +8,11 @@ use pest::iterators::Pair;
use pest::Parser;
use std::cmp::Ordering;
use std::collections::HashMap;
use shared::model::{FieldGetAccessor, FieldSetAccessor, PlaylistItemType};
use crate::model::{PlaylistItem, ItemField};
use shared::model::{FieldGetAccessor, FieldSetAccessor, ItemField, PlaylistItemType};
use crate::model::{PlaylistItem};
use crate::tools::directed_graph::DirectedGraph;
use crate::tuliprox_error::{create_tuliprox_error_result, info_err};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, info_err};
use shared::error::{TuliproxError, TuliproxErrorKind};
use shared::utils::CONSTANTS;
pub fn get_field_value(pli: &PlaylistItem, field: ItemField) -> String {
+1 -1
View File
@@ -2,7 +2,7 @@
use crate::foundation::filter::{PatternTemplate, TemplateValue, ValueAccessor};
use crate::foundation::mapper::EvalResult::{AnyValue, Failure, Named, Number, Undefined, Value};
use crate::tuliprox_error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use shared::utils::Capitalize;
use log::{debug, trace};
use pest::iterators::{Pair, Pairs};
+1 -12
View File
@@ -2,18 +2,7 @@ use std::sync::Arc;
use crate::model::{MessagingConfig};
use log::{debug, error};
use reqwest::{header};
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum MsgKind {
#[serde(rename = "info")]
Info,
#[serde(rename = "stats")]
Stats,
#[serde(rename = "error")]
Error,
#[serde(rename = "watch")]
Watch,
}
use shared::model::MsgKind;
fn is_enabled(kind: MsgKind, cfg: &MessagingConfig) -> bool {
cfg.notify_on.contains(&kind)
+1 -82
View File
@@ -1,11 +1,8 @@
use arc_swap::{ArcSwapOption};
use enum_iterator::Sequence;
use std::collections::{HashSet};
use std::fmt::Display;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
use log::{debug, error, warn};
@@ -14,7 +11,7 @@ use rand::Rng;
use crate::model::{ApiProxyConfig, ApiProxyServerInfo, Mappings, ProxyUserCredentials, SourcesConfig};
use crate::model::{ConfigInput, ConfigInputOptions, ConfigTarget, HdHomeRunConfig, IpCheckConfig, LogConfig, MessagingConfig, ProxyConfig, TargetOutput, VideoConfig, WebUiConfig};
use crate::tuliprox_error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use shared::utils::{parse_to_kbps, default_connect_timeout_secs, default_grace_period_millis, default_grace_period_timeout_secs};
const STREAM_QUEUE_SIZE: usize = 1024; // mpsc channel holding messages. with 8192byte chunks and 2Mbit/s approx 8MB
@@ -43,84 +40,6 @@ use crate::api::model::streams::transport_stream_buffer::TransportStreamBuffer;
use crate::model::config_cache::CacheConfig;
use crate::utils;
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, Eq, PartialEq)]
pub enum ItemField {
#[serde(rename = "group")]
Group,
#[serde(rename = "name")]
Name,
#[serde(rename = "title")]
Title,
#[serde(rename = "url")]
Url,
#[serde(rename = "input")]
Input,
#[serde(rename = "type")]
Type,
#[serde(rename = "caption")]
Caption,
}
impl ItemField {
const GROUP: &'static str = "Group";
const NAME: &'static str = "Name";
const TITLE: &'static str = "Title";
const URL: &'static str = "Url";
const INPUT: &'static str = "Input";
const TYPE: &'static str = "Type";
const CAPTION: &'static str = "Caption";
pub fn as_str(&self) -> &'static str {
match *self {
Self::Group => Self::GROUP,
Self::Name => Self::NAME,
Self::Title => Self::TITLE,
Self::Url => Self::URL,
Self::Input => Self::INPUT,
Self::Type => Self::TYPE,
Self::Caption => Self::CAPTION,
}
}
}
impl Display for ItemField {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", match *self {
Self::Group => Self::GROUP,
Self::Name => Self::NAME,
Self::Title => Self::TITLE,
Self::Url => Self::URL,
Self::Input => Self::INPUT,
Self::Type => Self::TYPE,
Self::Caption => Self::CAPTION,
})
}
}
impl FromStr for ItemField {
type Err = TuliproxError;
fn from_str(s: &str) -> Result<Self, TuliproxError> {
if s.eq_ignore_ascii_case(Self::GROUP) {
Ok(Self::Group)
} else if s.eq_ignore_ascii_case(Self::NAME) {
Ok(Self::Name)
} else if s.eq_ignore_ascii_case(Self::TITLE) {
Ok(Self::Title)
} else if s.eq_ignore_ascii_case(Self::CAPTION) {
Ok(Self::Caption)
} else if s.eq_ignore_ascii_case(Self::URL) {
Ok(Self::Url)
} else if s.eq_ignore_ascii_case(Self::INPUT) {
Ok(Self::Input)
} else if s.eq_ignore_ascii_case(Self::TYPE) {
Ok(Self::Type)
} else {
create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown InputType: {}", s)
}
}
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum FilterMode {
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::api::model::app_state::AppState;
use crate::tuliprox_error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use crate::model::{Config};
use crate::repository::user_repository::{backup_api_user_db_file, get_api_user_db_path, load_api_user, merge_api_user};
use crate::utils::{save_api_proxy};
+1 -1
View File
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use log::error;
use path_clean::PathClean;
use crate::tuliprox_error::{info_err, TuliproxError, TuliproxErrorKind};
use shared::error::{info_err, TuliproxError, TuliproxErrorKind};
use shared::utils::parse_size_base_2;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use regex::Regex;
use std::borrow::BorrowMut;
use std::collections::HashMap;
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use shared::utils::CONSTANTS;
use log::warn;
use regex::Regex;
+1 -1
View File
@@ -1,6 +1,6 @@
use std::collections::HashSet;
use log::warn;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
use shared::error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
fn default_friendly_name() -> String { String::from("TuliproxTV") }
fn default_manufacturer() -> String { String::from("Silicondust") }
fn default_model_name() -> String { String::from("HDTC-2US") }
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliproxError, TuliproxErrorKind};
use crate::model::{EpgConfig};
use shared::utils::default_as_true;
use shared::utils::get_trimmed_string;
+1 -1
View File
@@ -1,5 +1,5 @@
use regex::Regex;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::messaging::MsgKind;
use shared::model::MsgKind;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
+2 -2
View File
@@ -1,6 +1,6 @@
use shared::model::ItemField;
use crate::foundation::filter::{apply_templates_to_pattern_single, PatternTemplate};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
use crate::model::ItemField;
use shared::error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
+2 -2
View File
@@ -1,7 +1,7 @@
use regex::Regex;
use crate::foundation::filter::{apply_templates_to_pattern, apply_templates_to_pattern_single, PatternTemplate, TemplateValue};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error, handle_tuliprox_error_result_list};
use crate::model::{ItemField};
use shared::error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error, handle_tuliprox_error_result_list};
use shared::model::ItemField;
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum SortOrder {
+1 -1
View File
@@ -1,6 +1,6 @@
use std::collections::{HashMap, HashSet};
use crate::foundation::filter::{prepare_templates, PatternTemplate};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, handle_tuliprox_error_result_list, create_tuliprox_error_result};
use shared::error::{TuliproxError, TuliproxErrorKind, handle_tuliprox_error_result_list, create_tuliprox_error_result};
use crate::model::{ConfigInput, ProcessTargets};
use crate::model::config_target::ConfigTarget;
use shared::utils::default_as_default;
+3 -27
View File
@@ -2,40 +2,16 @@ use crate::foundation::filter::{get_filter, Filter, PatternTemplate, ValueProvid
use crate::model::config_rename::ConfigRename;
use crate::model::config_sort::ConfigSort;
use crate::model::mapping::Mapping;
use crate::model::processing_order::ProcessingOrder;
use crate::model::trakt::TraktConfig;
use crate::tuliprox_error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err, TuliproxError, TuliproxErrorKind};
use shared::utils::{default_as_default, default_as_true, default_resolve_delay_secs};
use arc_swap::ArcSwapOption;
use enum_iterator::Sequence;
use shared::model::ClusterFlags;
use shared::model::{ClusterFlags, ProcessingOrder, StrmExportStyle};
use shared::model::PlaylistItemType;
use std::fmt::Display;
use std::sync::Arc;
#[derive(
Debug,
Copy,
Clone,
serde::Serialize,
serde::Deserialize,
Sequence,
PartialEq,
Eq,
Hash,
Default
)]
pub enum ExportStyle {
#[serde(rename = "kodi")]
#[default]
Kodi,
#[serde(rename = "plex")]
Plex,
#[serde(rename = "emby")]
Emby,
#[serde(rename = "jellyfin")]
Jellyfin,
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq, Hash)]
pub enum TargetType {
@@ -165,7 +141,7 @@ pub struct StrmTargetOutput {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(default)]
pub style: ExportStyle,
pub style: StrmExportStyle,
#[serde(default)]
pub flat: bool,
#[serde(default)]
+1 -1
View File
@@ -3,7 +3,7 @@ use std::fs::File;
use std::io::BufRead;
use std::path::PathBuf;
use crate::auth::UserCredential;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
use shared::error::{TuliproxError, TuliproxErrorKind, create_tuliprox_error_result};
use crate::utils;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+1 -1
View File
@@ -1,5 +1,5 @@
use shared::utils::default_as_true;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::WebAuthConfig;
const RESERVED_PATHS: &[&str] = &[
+2 -2
View File
@@ -7,8 +7,8 @@ use log::{trace};
use crate::foundation::filter::{apply_templates_to_pattern_single, get_filter, prepare_templates, Filter, PatternTemplate};
use crate::foundation::mapper::MapperScript;
use crate::model::valid_property;
use crate::tuliprox_error::{create_tuliprox_error_result, info_err};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, info_err};
use shared::error::{TuliproxError, TuliproxErrorKind};
pub const COUNTER_FIELDS: &[&str] = &["name", "title", "caption", "chno"];
-2
View File
@@ -13,7 +13,6 @@ mod config_hdhomerun;
mod config_ip_check;
mod config_sort;
mod config_rename;
mod processing_order;
mod config_target;
mod config_source;
mod config_proxy;
@@ -42,7 +41,6 @@ pub use self::config_webui::*;
pub use self::config_ip_check::*;
pub use self::config_sort::*;
pub use self::config_rename::*;
pub use self::processing_order::*;
pub use self::config_target::*;
pub use self::config_source::*;
pub use self::config_proxy::*;
-1
View File
@@ -8,7 +8,6 @@ macro_rules! include_modules {
extern crate pest_derive;
pub mod api;
pub mod auth;
pub mod tuliprox_error;
pub mod messaging;
pub mod model;
pub mod processing;
+1 -1
View File
@@ -544,7 +544,7 @@ mod tests {
assert_eq!("odisea.bg", normalize_channel_name("BG | ODISEA ᵁᴴᴰ ³⁸⁴⁰ᴾ", &epg_smart_cfg));
}
use crate::tuliprox_error::TuliproxError;
use shared::error::TuliproxError;
use rphonetic::{Encoder, Metaphone};
#[test]
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use crate::model::ConfigInput;
use crate::model::{PlaylistGroup, PlaylistItem, PlaylistItemHeader};
use crate::model::{XtreamCategory, XtreamSeriesInfo, XtreamSeriesInfoEpisode, XtreamStream};
+2 -1
View File
@@ -2,7 +2,8 @@ use std::collections::BTreeSet;
use std::path::{Path};
use std::sync::Arc;
use log::{error, info};
use crate::messaging::{MsgKind, send_message};
use shared::model::MsgKind;
use crate::messaging::{send_message};
use crate::model::Config;
use crate::model::PlaylistGroup;
use crate::utils;
+4 -4
View File
@@ -10,17 +10,17 @@ use std::thread;
use tokio::sync::Mutex;
use crate::foundation::filter::{get_field_value, set_field_value, ValueProvider, ValueAccessor};
use crate::messaging::{send_message, MsgKind};
use crate::model::{ConfigTarget, InputType, ItemField, ProcessTargets, ProcessingOrder};
use crate::messaging::{send_message};
use crate::model::{ConfigTarget, InputType, ProcessTargets};
use crate::model::{CounterModifier, Mapping};
use crate::model::{FetchedPlaylist, PlaylistGroup, PlaylistItem};
use shared::model::{FieldGetAccessor, FieldSetAccessor, PlaylistEntry, UUIDType, XtreamCluster};
use shared::model::{FieldGetAccessor, FieldSetAccessor, ItemField, MsgKind, PlaylistEntry, ProcessingOrder, UUIDType, XtreamCluster};
use crate::model::{InputStats, PlaylistStats, SourceStats, TargetStats};
use crate::processing::playlist_watch::process_group_watch;
use crate::processing::processor::xtream_series::playlist_resolve_series;
use crate::processing::processor::trakt::process_trakt_categories_for_target;
use crate::repository::playlist_repository::persist_playlist;
use crate::tuliprox_error::{get_errors_notify_message, notify_err, TuliproxError, TuliproxErrorKind};
use shared::error::{get_errors_notify_message, notify_err, TuliproxError, TuliproxErrorKind};
use crate::utils::debug_if_enabled;
use shared::utils::default_as_default;
use deunicode::deunicode;
+1 -1
View File
@@ -2,7 +2,7 @@ use std::borrow::Cow;
use crate::model::{ConfigTarget, PlaylistGroup, PlaylistItem};
use shared::model::{FieldGetAccessor, FieldSetAccessor, XtreamCluster};
use crate::model::{TraktConfig, TraktContentType, TraktListConfig, TraktListItem, TraktMatchItem, TraktMatchResult};
use crate::tuliprox_error::TuliproxError;
use shared::error::TuliproxError;
use crate::utils::trakt::TraktClient;
use crate::utils::trakt::extract_year_from_title;
use crate::utils::trakt::normalize_title_for_matching;
+2 -2
View File
@@ -1,5 +1,5 @@
use crate::tuliprox_error::{info_err, notify_err};
use crate::tuliprox_error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use shared::error::{info_err, notify_err};
use shared::error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use crate::model::{Config, ConfigInput};
use crate::model::{FetchedPlaylist, PlaylistItem};
use shared::model::{PlaylistEntry, PlaylistItemType, XtreamCluster};
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::{Config, ConfigTarget, InputType};
use crate::model::{FetchedPlaylist, PlaylistGroup, PlaylistItem};
use shared::model::{PlaylistItemType, XtreamCluster};
@@ -8,7 +8,7 @@ use crate::processing::processor::xtream::{create_resolve_episode_wal_files, cre
use crate::repository::storage::get_input_storage_path;
use crate::repository::xtream_repository::{write_series_info_to_wal_file, xtream_get_info_file_paths, xtream_update_input_info_file, xtream_update_input_series_episodes_record_from_wal_file, xtream_update_input_series_record_from_wal_file};
use crate::repository::IndexedDocumentReader;
use crate::tuliprox_error::{notify_err, info_err};
use shared::error::{notify_err, info_err};
use crate::processing::processor::{handle_error, handle_error_and_return, create_resolve_options_function_for_xtream_target};
use std::collections::HashMap;
use std::fs::File;
@@ -1,10 +1,10 @@
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::{Config, ConfigTarget, InputType};
use crate::model::{FetchedPlaylist, PlaylistItem};
use shared::model::{PlaylistItemType, XtreamCluster};
use crate::processing::processor::xtream::{create_resolve_info_wal_files, playlist_resolve_download_playlist_item, read_processed_info_ids, should_update_info};
use crate::repository::xtream_repository::{write_vod_info_to_wal_file, xtream_update_input_info_file, xtream_update_input_vod_record_from_wal_file, InputVodInfoRecord};
use crate::tuliprox_error::{notify_err};
use shared::error::{notify_err};
use crate::processing::processor::{handle_error, handle_error_and_return, create_resolve_options_function_for_xtream_target};
use crate::utils::{get_u32_from_serde_value, get_u64_from_serde_value, get_string_from_serde_value};
use crate::repository::xtream_repository::xtream_get_input_info;
+1 -1
View File
@@ -3,7 +3,7 @@ use std::io::{self, BufReader, Read, Seek, SeekFrom, Write};
use std::marker::PhantomData;
use std::mem::size_of;
use std::path::Path;
use crate::tuliprox_error::{str_to_io_error, to_io_error};
use shared::error::{str_to_io_error, to_io_error};
use log::error;
use ruzstd::decoding::StreamingDecoder;
use ruzstd::encoding::{compress_to_vec, CompressionLevel};
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{notify_err, TuliproxError, TuliproxErrorKind};
use shared::error::{notify_err, TuliproxError, TuliproxErrorKind};
use crate::model::{Config, ConfigTarget, TargetOutput};
use crate::model::Epg;
use crate::repository::m3u_repository::m3u_get_epg_file_path;
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::repository::bplustree::{BPlusTree, BPlusTreeQuery};
use log::error;
use serde::{Deserialize, Serialize};
use tempfile::NamedTempFile;
use crate::tuliprox_error::{str_to_io_error, to_io_error};
use shared::error::{str_to_io_error, to_io_error};
use crate::utils;
use crate::utils::{bincode_deserialize, bincode_serialize};
@@ -1,5 +1,5 @@
use crate::tuliprox_error::info_err;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::info_err;
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::{ProxyType, ProxyUserCredentials};
use crate::model::{Config, ConfigTarget, ConfigTargetOptions, TargetType};
use crate::model::{M3uPlaylistItem};
+2 -2
View File
@@ -1,5 +1,5 @@
use crate::tuliprox_error::{create_tuliprox_error,info_err};
use crate::tuliprox_error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error,info_err};
use shared::error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use crate::model::ProxyUserCredentials;
use crate::model::{Config, ConfigTarget, M3uTargetOutput};
use crate::model::{M3uPlaylistItem, PlaylistGroup, PlaylistItem};
@@ -1,5 +1,5 @@
use crate::tuliprox_error::info_err;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::info_err;
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::{Config, ConfigTarget, TargetOutput};
use crate::model::{PlaylistGroup};
use shared::model::{PlaylistItemType};
+2 -2
View File
@@ -1,7 +1,7 @@
use std::path::{Path, PathBuf};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::{Config};
use crate::tuliprox_error::{notify_err};
use shared::error::{notify_err};
use crate::repository::storage_const;
use crate::utils;
+12 -13
View File
@@ -1,9 +1,8 @@
use crate::tuliprox_error::{create_tuliprox_error_result, info_err};
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, info_err};
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::{ApiProxyServerInfo, ProxyUserCredentials};
use crate::model::{Config, ConfigTarget, StrmTargetOutput};
use crate::model::{PlaylistGroup, PlaylistItem, ExportStyle,
};
use crate::model::{PlaylistGroup, PlaylistItem};
use crate::model::XtreamSeriesEpisode;
use crate::repository::bplustree::BPlusTree;
use crate::repository::storage::{ensure_target_storage_path, get_input_storage_path};
@@ -23,7 +22,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::fs::{create_dir_all, remove_dir, remove_file, File};
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter};
use shared::model::{ClusterFlags, FieldGetAccessor, PlaylistItemType, UUIDType};
use shared::model::{ClusterFlags, FieldGetAccessor, PlaylistItemType, StrmExportStyle, UUIDType};
use crate::utils;
/// Sanitizes a string to be safe for use as a file or directory name by
@@ -673,7 +672,7 @@ async fn style_based_rename(
cfg: &Config,
strm_item_info: &StrmItemInfo,
input_tmdb_indexes: &mut InputTmdbIndexMap,
style: &ExportStyle,
style: &StrmExportStyle,
underscore_whitespace: bool,
flat: bool,
) -> (PathBuf, String) {
@@ -694,10 +693,10 @@ async fn style_based_rename(
// Dispatch the call to the responsible function based on the style.
match style {
ExportStyle::Kodi => format_for_kodi(strm_item_info, tmdb_id, separator, flat),
ExportStyle::Plex => format_for_plex(strm_item_info, tmdb_id, separator, flat),
ExportStyle::Emby => format_for_emby(strm_item_info, tmdb_id, separator, flat),
ExportStyle::Jellyfin => format_for_jellyfin(strm_item_info, tmdb_id, separator, flat),
StrmExportStyle::Kodi => format_for_kodi(strm_item_info, tmdb_id, separator, flat),
StrmExportStyle::Plex => format_for_plex(strm_item_info, tmdb_id, separator, flat),
StrmExportStyle::Emby => format_for_emby(strm_item_info, tmdb_id, separator, flat),
StrmExportStyle::Jellyfin => format_for_jellyfin(strm_item_info, tmdb_id, separator, flat),
}
}
@@ -706,7 +705,7 @@ async fn prepare_strm_files(
new_playlist: &mut [PlaylistGroup],
_root_path: &Path,
underscore_whitespace: bool,
style: &ExportStyle,
style: &StrmExportStyle,
flat: bool,
) -> Vec<StrmFile> {
let channel_count = new_playlist
@@ -769,13 +768,13 @@ async fn prepare_strm_files(
// A style-specific match is not strictly necessary here but is good practice for future flexibility.
let new_filename = match style {
// Plex, Emby, and Kodi all follow the `Filename - Suffix` pattern.
ExportStyle::Plex | ExportStyle::Emby | ExportStyle::Kodi => {
StrmExportStyle::Plex | StrmExportStyle::Emby | StrmExportStyle::Kodi => {
format!("{base_filename}{version_separator}{version_label}")
}
// Jellyfin also follows this pattern, but explicitly shows an option for " - [Label]".
// Using brackets makes the version distinct and is a clean implementation.
ExportStyle::Jellyfin => {
StrmExportStyle::Jellyfin => {
format!("{base_filename}{version_separator}[{version_label}]")
}
};
@@ -1,8 +1,8 @@
use std::collections::HashSet;
use log::error;
use shared::model::XtreamCluster;
use crate::tuliprox_error::info_err;
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::info_err;
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::{ProxyUserCredentials};
use crate::model::{Config, ConfigTarget, TargetType};
use crate::model::{XtreamPlaylistItem};
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{create_tuliprox_error, create_tuliprox_error_result, info_err, notify_err, str_to_io_error, to_io_error, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error, create_tuliprox_error_result, info_err, notify_err, str_to_io_error, to_io_error, TuliproxError, TuliproxErrorKind};
use crate::model::{ProxyUserCredentials};
use crate::model::{Config, ConfigInput, ConfigTarget, XtreamTargetOutput};
use crate::model::{PlaylistGroup, PlaylistItem, XtreamPlaylistItem};
+1 -1
View File
@@ -1,5 +1,5 @@
use std::io;
use crate::tuliprox_error::to_io_error;
use shared::error::to_io_error;
#[inline]
pub fn bincode_serialize<T>(value: &T) -> io::Result<Vec<u8>>
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
use base64::{engine::general_purpose, Engine as _};
use openssl::symm::{Cipher, Crypter, Mode};
use rand::Rng;
+1 -3
View File
@@ -1,6 +1,6 @@
use crate::model::{ApiProxyConfig, SourcesConfig};
use crate::model::{Config, ConfigDto};
use crate::tuliprox_error::{create_tuliprox_error, info_err, to_io_error, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error, info_err, to_io_error, TuliproxError, TuliproxErrorKind};
use crate::utils::{open_file, EnvResolvingReader};
use crate::utils::{file_reader};
use crate::utils::sys_utils::exit;
@@ -73,8 +73,6 @@ pub fn read_sources(sources_file: &str, resolve_env: bool, include_computed: boo
}
}
pub fn read_config(config_path: &str, config_file: &str, sources_file: &str, api_proxy_file: &str, mappings_file: Option<String>, include_computed: bool) -> Result<Config, TuliproxError> {
let resolve_env = true;
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::model::{ConfigInput, ConfigInputAlias, InputType};
use crate::tuliprox_error::{str_to_io_error, to_io_error};
use shared::error::{str_to_io_error, to_io_error};
use crate::utils::request::{get_credentials_from_url, get_local_file_content};
use crate::utils::EnvResolvingReader;
use crate::utils::{file_reader, resolve_relative_path};
+1 -1
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use std::{fmt, io};
use std::path::{Path, PathBuf};
use tokio::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::tuliprox_error::str_to_io_error;
use shared::error::str_to_io_error;
#[derive(Clone)]
pub struct FileLockManager {
+1 -1
View File
@@ -5,7 +5,7 @@ use std::io::{BufReader, BufWriter, Read, Write};
use std::path::{Path, PathBuf};
use std::{env, fs};
use crate::tuliprox_error::str_to_io_error;
use shared::error::str_to_io_error;
use crate::utils::debug_if_enabled;
use shared::utils::{API_PROXY_FILE, CONFIG_FILE, CONFIG_PATH, MAPPING_FILE, SOURCE_FILE, USER_FILE};
use log::{debug, error};
+1 -1
View File
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use crate::foundation::filter::PatternTemplate;
use crate::model::{Mapping, MappingDefinition, Mappings};
use crate::tuliprox_error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
use crate::utils::traverse_dir;
use crate::utils::{config_file_reader, open_file};
use log::{warn};
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::TuliproxError;
use shared::error::TuliproxError;
use crate::model::{Config, ConfigInput, PersistedEpgSource};
use crate::model::TVGuide;
use crate::utils::{add_prefix_to_filename, cleanup_unlisted_files_with_suffix, prepare_file_path, short_hash};
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{TuliproxError, TuliproxErrorKind};
use shared::error::{TuliproxError, TuliproxErrorKind};
use crate::model::IpCheckConfig;
use regex::Regex;
use reqwest::Client;
+1 -1
View File
@@ -1,5 +1,5 @@
use std::sync::Arc;
use crate::tuliprox_error::TuliproxError;
use shared::error::TuliproxError;
use crate::model::{Config, ConfigInput};
use crate::model::PlaylistGroup;
use crate::processing::parser::m3u;
+2 -2
View File
@@ -15,8 +15,8 @@ use reqwest::header::CONTENT_ENCODING;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use url::Url;
use crate::tuliprox_error::create_tuliprox_error_result;
use crate::tuliprox_error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use shared::error::create_tuliprox_error_result;
use shared::error::{str_to_io_error, TuliproxError, TuliproxErrorKind};
use crate::model::{format_elapsed_time, Config};
use crate::model::{ConfigInput, InputFetchMethod};
use crate::repository::storage::{get_input_storage_path};
+3 -3
View File
@@ -4,7 +4,7 @@ use crate::model::{PlaylistGroup, XtreamPlaylistItem};
use crate::processing::parser::xtream;
use crate::repository::xtream_repository;
use crate::repository::xtream_repository::{rewrite_xtream_series_info_content, rewrite_xtream_vod_info_content, xtream_get_input_info};
use crate::tuliprox_error::{str_to_io_error, TuliproxError};
use shared::error::{str_to_io_error, TuliproxError};
use crate::utils;
use crate::utils::{get_string_from_serde_value, request};
use crate::utils::request::extract_extension_from_url;
@@ -15,8 +15,8 @@ use std::io::Error;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use shared::model::{PlaylistEntry, XtreamCluster};
use crate::messaging::{send_message, MsgKind};
use shared::model::{MsgKind, PlaylistEntry, XtreamCluster};
use crate::messaging::{send_message};
#[inline]
pub fn get_xtream_stream_url_base(url: &str, username: &str, password: &str) -> String {
+2 -2
View File
@@ -1,9 +1,9 @@
use crate::model::{TraktApiConfig, TraktListConfig, TraktListItem};
use crate::tuliprox_error::TuliproxError;
use shared::error::TuliproxError;
use reqwest::header::{HeaderMap, HeaderValue};
use std::sync::Arc;
use log::{debug, info};
use crate::tuliprox_error::{info_err, TuliproxErrorKind};
use shared::error::{info_err, TuliproxErrorKind};
use super::errors::{handle_trakt_api_error};
pub struct TraktClient {
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::tuliprox_error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use shared::error::{create_tuliprox_error_result, TuliproxError, TuliproxErrorKind};
use reqwest::StatusCode;
/// Handle Trakt API response status and convert to appropriate error
+1
View File
@@ -8,3 +8,4 @@ bitflags = "2.9.1"
serde = { version = "1.0.219", features = ["derive"] }
rand = "0.9.1"
regex = "1.11.1"
enum-iterator = "2"
+3
View File
@@ -0,0 +1,3 @@
mod tuliprox_error;
pub use tuliprox_error::*;
+1
View File
@@ -1,2 +1,3 @@
pub mod error;
pub mod model;
pub mod utils;
+56
View File
@@ -0,0 +1,56 @@
use crate::model::{WebUiConfigDto, MessagingConfigDto, IpCheckConfigDto, HdHomeRunConfigDto, VideoConfigDto, ScheduleConfigDto, LogConfigDto, ReverseProxyConfigDto, ProxyConfigDto};
use crate::utils::{default_connect_timeout_secs};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfigApiDto {
pub host: String,
pub port: u16,
#[serde(default)]
pub web_root: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfigDto {
#[serde(default)]
pub threads: u8,
pub api: ConfigApiDto,
pub working_dir: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub backup_dir: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_config_dir: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mapping_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_stream_response_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video: Option<VideoConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schedules: Option<Vec<ScheduleConfigDto>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub log: Option<LogConfigDto>,
#[serde(default)]
pub user_access_control: bool,
#[serde(default = "default_connect_timeout_secs")]
pub connect_timeout_secs: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sleep_timer_mins: Option<u32>,
#[serde(default)]
pub update_on_boot: bool,
#[serde(default)]
pub config_hot_reload: bool,
#[serde(default)]
pub web_ui: Option<WebUiConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub messaging: Option<MessagingConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reverse_proxy: Option<ReverseProxyConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hdhomerun: Option<HdHomeRunConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proxy: Option<ProxyConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ipcheck: Option<IpCheckConfigDto>,
}
+10
View File
@@ -0,0 +1,10 @@
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct CacheConfigDto {
#[serde(default)]
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dir: Option<String>,
}
+49
View File
@@ -0,0 +1,49 @@
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EpgSourceDto {
pub url: String,
#[serde(default)]
pub priority: i16,
#[serde(default)]
pub logo_override: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum EpgNamePrefix {
#[default]
Ignore,
Suffix(String),
Prefix(String),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EpgSmartMatchConfigDto {
#[serde(default)]
pub enabled: bool,
pub normalize_regex: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strip: Option<Vec<String>>,
#[serde(default)]
pub name_prefix: EpgNamePrefix,
#[serde(default)]
pub name_prefix_separator: Option<Vec<char>>,
#[serde(default)]
pub fuzzy_matching: bool,
#[serde(default)]
pub match_threshold: u16,
#[serde(default)]
pub best_match_threshold: u16,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EpgConfigDto {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sources: Option<Vec<EpgSourceDto>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub smart_match: Option<EpgSmartMatchConfigDto>,
}
+45
View File
@@ -0,0 +1,45 @@
fn default_friendly_name() -> String { String::from("TuliproxTV") }
fn default_manufacturer() -> String { String::from("Silicondust") }
fn default_model_name() -> String { String::from("HDTC-2US") }
fn default_firmware_name() -> String { String::from("hdhomeruntc_atsc") }
fn default_firmware_version() -> String { String::from("20170930") }
fn default_device_type() -> String { String::from("urn:schemas-upnp-org:device:MediaServer:1") }
fn default_device_udn() -> String { String::from("uuid:12345678-90ab-cdef-1234-567890abcdef::urn:dial-multicast:com.silicondust.hdhomerun") }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct HdHomeRunDeviceConfigDto {
#[serde(default = "default_friendly_name")]
pub friendly_name: String,
#[serde(default = "default_manufacturer")]
pub manufacturer: String,
#[serde(default = "default_model_name")]
pub model_name: String,
#[serde(default = "default_model_name")]
pub model_number: String,
#[serde(default = "default_firmware_name")]
pub firmware_name: String,
#[serde(default = "default_firmware_version")]
pub firmware_version: String,
// pub device_auth: String,
#[serde(default = "default_device_type")]
pub device_type: String,
#[serde(default = "default_device_udn")]
pub device_udn: String,
pub name: String,
#[serde(default)]
pub port: u16,
#[serde(default)]
pub tuner_count: u8,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct HdHomeRunConfigDto {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub auth: bool,
pub devices: Vec<HdHomeRunDeviceConfigDto>,
}
+91
View File
@@ -0,0 +1,91 @@
use std::collections::HashMap;
use enum_iterator::Sequence;
use crate::model::{EpgConfigDto};
use crate::utils::{default_as_true};
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence,
PartialEq, Eq, Default)]
pub enum InputType {
#[serde(rename = "m3u")]
#[default]
M3u,
#[serde(rename = "xtream")]
Xtream,
#[serde(rename = "m3u_batch")]
M3uBatch,
#[serde(rename = "xtream_batch")]
XtreamBatch,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfigInputOptionsDto {
#[serde(default)]
pub xtream_skip_live: bool,
#[serde(default)]
pub xtream_skip_vod: bool,
#[serde(default)]
pub xtream_skip_series: bool,
#[serde(default = "default_as_true")]
pub xtream_live_stream_use_prefix: bool,
#[serde(default)]
pub xtream_live_stream_without_extension: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfigInputAliasDto {
#[serde(skip)]
pub id: u16,
pub name: String,
pub url: String,
pub username: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(default)]
pub priority: i16,
#[serde(default)]
pub max_connections: u16,
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence,
PartialEq, Eq, Default)]
pub enum InputFetchMethod {
#[default]
GET,
POST,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfigInputDto {
#[serde(skip)]
pub id: u16,
pub name: String,
#[serde(default, rename = "type")]
pub input_type: InputType,
#[serde(default)]
pub headers: HashMap<String, String>,
pub url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub epg: Option<EpgConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub persist: Option<String>,
#[serde(default = "default_as_true")]
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub options: Option<ConfigInputOptionsDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub aliases: Option<Vec<ConfigInputAliasDto>>,
#[serde(default)]
pub priority: i16,
#[serde(default)]
pub max_connections: u16,
#[serde(default)]
pub method: InputFetchMethod,
}
+24
View File
@@ -0,0 +1,24 @@
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct IpCheckConfigDto {
/// URL that may return both IPv4 and IPv6 in one response
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
/// Dedicated URL to fetch only IPv4
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_ipv4: Option<String>,
/// Dedicated URL to fetch only IPv6
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_ipv6: Option<String>,
/// Optional regex pattern to extract IPv4
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pattern_ipv4: Option<String>,
/// Optional regex pattern to extract IPv6
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pattern_ipv6: Option<String>,
}
+12
View File
@@ -0,0 +1,12 @@
use crate::utils::{default_as_true};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct LogConfigDto {
#[serde(default = "default_as_true")]
pub sanitize_sensitive_info: bool,
#[serde(default)]
pub log_active_user: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub log_level: Option<String>,
}
+36
View File
@@ -0,0 +1,36 @@
use crate::model::MsgKind;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TelegramMessagingConfigDto {
pub bot_token: String,
pub chat_ids: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RestMessagingConfigDto {
pub url: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PushoverMessagingConfigDto {
pub url: Option<String>,
pub token: String,
pub user: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct MessagingConfigDto {
#[serde(default)]
pub notify_on: Vec<MsgKind>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub telegram: Option<TelegramMessagingConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rest: Option<RestMessagingConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pushover: Option<PushoverMessagingConfigDto>,
}
+38
View File
@@ -0,0 +1,38 @@
mod trakt;
mod base;
mod web;
mod messaging;
mod hdhomerun;
mod ipcheck;
mod source;
mod target;
mod sort;
mod video;
mod schedule;
mod log;
mod input;
mod stream;
mod epg;
mod reverse_proxy;
mod cache;
mod rate_limit;
mod proxy;
pub use base::*;
pub use web::*;
pub use messaging::*;
pub use hdhomerun::*;
pub use ipcheck::*;
pub use source::*;
pub use sort::*;
pub use video::*;
pub use schedule::*;
pub use log::*;
pub use input::*;
pub use stream::*;
pub use epg::*;
pub use cache::*;
pub use rate_limit::*;
pub use reverse_proxy::*;
pub use proxy::*;
pub use trakt::*;
+8
View File
@@ -0,0 +1,8 @@
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ProxyConfigDto {
pub url: String,
pub username: Option<String>,
pub password: Option<String>,
}
+7
View File
@@ -0,0 +1,7 @@
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct RateLimitConfigDto {
pub enabled: bool,
pub period_millis: u64,
pub burst_size: u32,
}
+16
View File
@@ -0,0 +1,16 @@
use crate::model::{CacheConfigDto, RateLimitConfigDto, StreamConfigDto};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ReverseProxyConfigDto {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream: Option<StreamConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache: Option<CacheConfigDto>,
#[serde(default)]
pub resource_rewrite_disabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rate_limit: Option<RateLimitConfigDto>,
#[serde(default)]
pub disable_referer_header: bool,
}
+9
View File
@@ -0,0 +1,9 @@
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ScheduleConfigDto {
#[serde(default)]
pub schedule: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub targets: Option<Vec<String>>,
}
+40
View File
@@ -0,0 +1,40 @@
use crate::model::ItemField;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigSortGroupDto {
pub order: SortOrder,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sequence: Option<Vec<String>>,
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum SortOrder {
#[serde(rename = "asc")]
Asc,
#[serde(rename = "desc")]
Desc,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigSortChannelDto {
// channel field
pub field: ItemField,
// match against group title
pub group_pattern: String,
pub order: SortOrder,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sequence: Option<Vec<String>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfigSortDto {
#[serde(default)]
pub match_as_ascii: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub groups: Option<ConfigSortGroupDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub channels: Option<Vec<ConfigSortChannelDto>>,
}
+41
View File
@@ -0,0 +1,41 @@
use crate::model::{ConfigInputDto, ItemField};
use crate::model::config::target::ConfigTargetDto;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum TemplateValue {
Single(String),
Multi(Vec<String>),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PatternTemplateDto {
pub name: String,
pub value: TemplateValue,
#[serde(skip)]
pub placeholder: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigRenameDto {
pub field: ItemField,
pub pattern: String,
pub new_name: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigSourceDto {
pub inputs: Vec<ConfigInputDto>,
pub targets: Vec<ConfigTargetDto>,
}
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SourcesConfigDto {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub templates: Option<Vec<PatternTemplateDto>>,
pub sources: Vec<ConfigSourceDto>,
}
+29
View File
@@ -0,0 +1,29 @@
use crate::utils::{default_grace_period_millis, default_grace_period_timeout_secs};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct StreamBufferConfigDto {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub size: usize,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct StreamConfigDto {
#[serde(default)]
pub retry: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub buffer: Option<StreamBufferConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub throttle: Option<String>,
#[serde(default = "default_grace_period_millis")]
pub grace_period_millis: u64,
#[serde(default = "default_grace_period_timeout_secs")]
pub grace_period_timeout_secs: u64,
#[serde(default)]
pub forced_retry_interval_secs: u32,
#[serde(default, skip)]
pub throttle_kbps: u64,
}
+112
View File
@@ -0,0 +1,112 @@
use crate::model::{ClusterFlags, ConfigSortDto, ProcessingOrder, StrmExportStyle, TargetType, TraktConfigDto};
use crate::model::config::source::ConfigRenameDto;
use crate::utils::{default_as_true, default_resolve_delay_secs, default_as_default};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfigTargetOptionsDto {
#[serde(default)]
pub ignore_logo: bool,
#[serde(default)]
pub share_live_streams: bool,
#[serde(default)]
pub remove_duplicates: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub force_redirect: Option<ClusterFlags>,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct XtreamTargetOutputDto {
#[serde(default = "default_as_true")]
pub skip_live_direct_source: bool,
#[serde(default = "default_as_true")]
pub skip_video_direct_source: bool,
#[serde(default = "default_as_true")]
pub skip_series_direct_source: bool,
#[serde(default)]
pub resolve_series: bool,
#[serde(default = "default_resolve_delay_secs")]
pub resolve_series_delay: u16,
#[serde(default)]
pub resolve_vod: bool,
#[serde(default = "default_resolve_delay_secs")]
pub resolve_vod_delay: u16,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trakt: Option<TraktConfigDto>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct M3uTargetOutputDto {
#[serde(skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
#[serde(default)]
pub include_type_in_url: bool,
#[serde(default)]
pub mask_redirect_url: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StrmTargetOutputDto {
pub directory: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(default)]
pub style: StrmExportStyle,
#[serde(default)]
pub flat: bool,
#[serde(default)]
pub underscore_whitespace: bool,
#[serde(default)]
pub cleanup: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub strm_props: Option<Vec<String>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HdHomeRunTargetOutputDto {
pub device: String,
pub username: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub use_output: Option<TargetType>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, tag = "type", rename_all = "lowercase")]
pub enum TargetOutput {
Xtream(XtreamTargetOutputDto),
M3u(M3uTargetOutputDto),
Strm(StrmTargetOutputDto),
HdHomeRun(HdHomeRunTargetOutputDto),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfigTargetDto {
#[serde(skip)]
pub id: u16,
#[serde(default = "default_as_true")]
pub enabled: bool,
#[serde(default = "default_as_default")]
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub options: Option<ConfigTargetOptionsDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sort: Option<ConfigSortDto>,
pub filter: String,
#[serde(default)]
pub output: Vec<TargetOutput>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rename: Option<Vec<ConfigRenameDto>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mapping: Option<Vec<String>>,
#[serde(default)]
pub processing_order: ProcessingOrder,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub watch: Option<Vec<String>>,
}
+49
View File
@@ -0,0 +1,49 @@
use serde::{Deserialize, Serialize};
fn default_fuzzy_threshold() -> u8 {
80
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TraktContentType {
Vod,
Series,
Both,
}
impl Default for TraktContentType {
fn default() -> Self {
Self::Both
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TraktApiConfigDto {
#[serde(default)]
pub(crate) key: String,
#[serde(default)]
pub(crate) version: String,
#[serde(default)]
pub(crate) url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TraktListConfigDto {
pub user: String,
pub list_slug: String,
pub category_name: String,
pub content_type: TraktContentType,
#[serde(default = "default_fuzzy_threshold")]
pub fuzzy_match_threshold: u8, // Percentage (0-100)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TraktConfigDto {
#[serde(default)]
pub api: TraktApiConfigDto,
pub lists: Vec<TraktListConfigDto>,
}
+25
View File
@@ -0,0 +1,25 @@
use std::collections::HashMap;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct VideoDownloadConfigDto {
#[serde(default)]
pub headers: HashMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub directory: Option<String>,
#[serde(default)]
pub organize_into_directories: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub episode_pattern: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct VideoConfigDto {
#[serde(default)]
pub extensions: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub download: Option<VideoDownloadConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub web_search: Option<String>,
}
+27
View File
@@ -0,0 +1,27 @@
use crate::utils::{default_as_true};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WebAuthConfigDto {
#[serde(default = "default_as_true")]
pub enabled: bool,
pub issuer: String,
pub secret: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub userfile: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct WebUiConfigDto {
#[serde(default = "default_as_true")]
pub enabled: bool,
#[serde(default = "default_as_true")]
pub user_ui_enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auth: Option<WebAuthConfigDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub player_server: Option<String>,
}
+83
View File
@@ -0,0 +1,83 @@
use std::fmt::Display;
use std::str::FromStr;
use enum_iterator::Sequence;
use crate::create_tuliprox_error_result;
use crate::error::{TuliproxError, TuliproxErrorKind};
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, Eq, PartialEq)]
pub enum ItemField {
#[serde(rename = "group")]
Group,
#[serde(rename = "name")]
Name,
#[serde(rename = "title")]
Title,
#[serde(rename = "url")]
Url,
#[serde(rename = "input")]
Input,
#[serde(rename = "type")]
Type,
#[serde(rename = "caption")]
Caption,
}
impl ItemField {
const GROUP: &'static str = "Group";
const NAME: &'static str = "Name";
const TITLE: &'static str = "Title";
const URL: &'static str = "Url";
const INPUT: &'static str = "Input";
const TYPE: &'static str = "Type";
const CAPTION: &'static str = "Caption";
pub fn as_str(&self) -> &'static str {
match *self {
Self::Group => Self::GROUP,
Self::Name => Self::NAME,
Self::Title => Self::TITLE,
Self::Url => Self::URL,
Self::Input => Self::INPUT,
Self::Type => Self::TYPE,
Self::Caption => Self::CAPTION,
}
}
}
impl Display for ItemField {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", match *self {
Self::Group => Self::GROUP,
Self::Name => Self::NAME,
Self::Title => Self::TITLE,
Self::Url => Self::URL,
Self::Input => Self::INPUT,
Self::Type => Self::TYPE,
Self::Caption => Self::CAPTION,
})
}
}
impl FromStr for ItemField {
type Err = TuliproxError;
fn from_str(s: &str) -> Result<Self, TuliproxError> {
if s.eq_ignore_ascii_case(Self::GROUP) {
Ok(Self::Group)
} else if s.eq_ignore_ascii_case(Self::NAME) {
Ok(Self::Name)
} else if s.eq_ignore_ascii_case(Self::TITLE) {
Ok(Self::Title)
} else if s.eq_ignore_ascii_case(Self::CAPTION) {
Ok(Self::Caption)
} else if s.eq_ignore_ascii_case(Self::URL) {
Ok(Self::Url)
} else if s.eq_ignore_ascii_case(Self::INPUT) {
Ok(Self::Input)
} else if s.eq_ignore_ascii_case(Self::TYPE) {
Ok(Self::Type)
} else {
create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown InputType: {}", s)
}
}
}
+12
View File
@@ -0,0 +1,12 @@
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum MsgKind {
#[serde(rename = "info")]
Info,
#[serde(rename = "stats")]
Stats,
#[serde(rename = "error")]
Error,
#[serde(rename = "watch")]
Watch,
}
+13 -1
View File
@@ -1,5 +1,17 @@
mod cluster_flags;
mod playlist;
mod messaging;
mod item_field;
mod target_type;
mod processing_order;
mod config;
mod strm_export_style;
pub use self::cluster_flags::*;
pub use self::playlist::*;
pub use self::playlist::*;
pub use self::messaging::*;
pub use self::item_field::*;
pub use self::target_type::*;
pub use self::processing_order::*;
pub use self::config::*;
pub use self::strm_export_style::*;
+15
View File
@@ -0,0 +1,15 @@
use enum_iterator::Sequence;
#[derive( Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence,
PartialEq, Eq, Hash, Default)]
pub enum StrmExportStyle {
#[serde(rename = "kodi")]
#[default]
Kodi,
#[serde(rename = "plex")]
Plex,
#[serde(rename = "emby")]
Emby,
#[serde(rename = "jellyfin")]
Jellyfin,
}
+32
View File
@@ -0,0 +1,32 @@
use std::fmt::Display;
use enum_iterator::Sequence;
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Sequence, PartialEq, Eq, Hash)]
pub enum TargetType {
#[serde(rename = "m3u")]
M3u,
#[serde(rename = "xtream")]
Xtream,
#[serde(rename = "strm")]
Strm,
#[serde(rename = "hdhomerun")]
HdHomeRun,
}
impl TargetType {
const M3U: &'static str = "M3u";
const XTREAM: &'static str = "Xtream";
const STRM: &'static str = "Strm";
const HDHOMERUN: &'static str = "HdHomeRun";
}
impl Display for TargetType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", match *self {
Self::M3u => Self::M3U,
Self::Xtream => Self::XTREAM,
Self::Strm => Self::STRM,
Self::HdHomeRun => Self::HDHOMERUN,
})
}
}