Feature/staged input and path improvements (#632)

* No template file not found error message
* Setup wizard improvmenets
* path improvements
* staged input fix
* path improvements
* Provider dns resolved ips now writing to a file instead of source.yml
* Playlist update hard kill after 1h
* matadata update tmdb fix
* Web UI fixes
* GeoIp update schedule
* batch:// prefix  for aliases
This commit is contained in:
euzu
2026-03-06 17:29:42 +01:00
committed by GitHub
parent c5632d0c98
commit 737d04d08b
82 changed files with 2506 additions and 1433 deletions
+11 -1
View File
@@ -7,6 +7,14 @@ use std::{
pub const DEFAULT_HOME_ENV_VAR: &str = "TULIPROX_HOME";
pub const DEFAULT_WEB_ROOT_ENV_VAR: &str = "TULIPROX_WEB_ROOT";
pub const DEFAULT_USER_AGENT: &str = "VLC/3.0.16 LibVLC/3.0.16";
pub const DEFAULT_BACKUP_DIR: &str = "backup";
pub const DEFAULT_CACHE_DIR: &str = "cache";
pub const DEFAULT_STORAGE_TEMP_DIR: &str = "tmp";
pub const DEFAULT_USER_CONFIG_DIR: &str = "user_config";
pub const DEFAULT_DOWNLOAD_DIR: &str = "downloads";
pub const DEFAULT_CUSTOM_STREAM_RESPONSE_PATH: &str = "resources";
pub const DNS_RESOLVED_FILE: &str = "provider_dns_resolved.json";
pub const USER_FILE: &str = "user.txt";
pub const CONFIG_PATH: &str = "config";
pub const CONFIG_FILE: &str = "config.yml";
@@ -15,7 +23,7 @@ pub const MAPPING_FILE: &str = "mapping.yml";
pub const TEMPLATE_FILE: &str = "template.yml";
pub const API_PROXY_FILE: &str = "api-proxy.yml";
pub const DEFAULT_WEB_DIR: &str = "web";
pub const DEFAULT_WORKING_DIR: &str = "data";
pub const DEFAULT_STORAGE_DIR: &str = "data"; // TODO rename to storage and use data for config, storage, ...
pub const DEFAULT_PORT: u16 = 8901;
pub const ENCODING_GZIP: &str = "gzip";
pub const ENCODING_DEFLATE: &str = "deflate";
@@ -31,6 +39,8 @@ pub const HLS_EXT_FRAGMENT: &str = ".m3u8#";
pub const DASH_EXT_QUERY: &str = ".mpd?";
pub const DASH_EXT_FRAGMENT: &str = ".mpd#";
pub const DEFAULT_EPISODE_PATTERN: &str = r".*(?P<episode>[Ss]\d{1,2}(.*?)[Ee]\d{1,2}).*";
pub const FILENAME_TRIM_PATTERNS: &[char] = &['.', '-', '_'];
const SUPPORTED_RESPONSE_HEADERS: &[&str] = &[
+114 -1
View File
@@ -1,4 +1,11 @@
use crate::model::{ConfigTargetOptions, LibraryMetadataFormat, ProcessingOrder};
use crate::{
model::{ConfigTargetOptions, LibraryMetadataFormat, MetadataUpdateConfigDto, ProcessingOrder, VideoConfigDto},
utils::{
CONFIG_PATH, DEFAULT_BACKUP_DIR, DEFAULT_CACHE_DIR, DEFAULT_CUSTOM_STREAM_RESPONSE_PATH, DEFAULT_DOWNLOAD_DIR,
DEFAULT_EPISODE_PATTERN, DEFAULT_STORAGE_DIR, DEFAULT_USER_AGENT, DEFAULT_USER_CONFIG_DIR, DEFAULT_WEB_DIR,
MAPPING_FILE, TEMPLATE_FILE, USER_FILE,
},
};
use std::sync::Arc;
pub const fn is_zero_u16(v: &u16) -> bool { *v == 0 }
@@ -218,6 +225,112 @@ pub fn is_default_metadata_ffprobe_live_probe_size(v: &String) -> bool {
*v == default_metadata_ffprobe_live_probe_size()
}
pub fn get_default_web_root() -> String { DEFAULT_WEB_DIR.to_string() }
pub fn is_blank_or_default_web_root(value: &str) -> bool {
let normalized = value.trim().replace('\\', "/");
if normalized.is_empty() {
return true;
}
let normalized = normalized.trim_end_matches('/');
normalized.trim_start_matches("./") == DEFAULT_WEB_DIR
}
pub fn is_default_dir_path(value: &str, default_dir: &str) -> bool {
let normalized = value.trim().replace('\\', "/");
let normalized = normalized.trim_end_matches('/');
let normalized = normalized.trim_start_matches("./");
let default_dir = default_dir.trim().replace('\\', "/");
let default_dir = default_dir.trim_end_matches('/');
normalized == default_dir
}
pub fn is_blank_or_default_download_dir(path: &Option<String>) -> bool {
path.as_ref().is_none_or(|value| value.trim().is_empty() || is_default_dir_path(value, DEFAULT_DOWNLOAD_DIR))
}
pub fn default_download_dir() -> Option<String> { Some(DEFAULT_DOWNLOAD_DIR.to_string()) }
pub fn default_episode_pattern() -> Option<String> { Some(DEFAULT_EPISODE_PATTERN.to_string()) }
pub fn is_blank_or_default_episode_pattern(path: &Option<String>) -> bool {
path.as_ref().is_none_or(|value| value.trim().is_empty() || value.trim() == DEFAULT_EPISODE_PATTERN)
}
pub fn is_blank_or_default_cache_dir(path: &Option<String>) -> bool {
path.as_ref().is_none_or(|value| value.trim().is_empty() || is_default_dir_path(value, DEFAULT_CACHE_DIR))
}
pub fn default_default_user_agent() -> Option<String> { Some(DEFAULT_USER_AGENT.to_string()) }
pub fn default_main_storage_dir() -> Option<String> { Some(DEFAULT_STORAGE_DIR.to_string()) }
pub fn default_main_backup_dir() -> Option<String> { Some(DEFAULT_BACKUP_DIR.to_string()) }
pub fn default_main_user_config_dir() -> Option<String> { Some(DEFAULT_USER_CONFIG_DIR.to_string()) }
pub fn default_main_mapping_path() -> Option<String> { Some(format!("./{CONFIG_PATH}/{MAPPING_FILE}")) }
pub fn default_main_template_path() -> Option<String> { Some(format!("./{CONFIG_PATH}/{TEMPLATE_FILE}")) }
pub fn default_custom_stream_response_path() -> Option<String> { Some(DEFAULT_CUSTOM_STREAM_RESPONSE_PATH.to_string()) }
pub fn default_user_file_path() -> Option<String> { Some(format!("./{CONFIG_PATH}/{USER_FILE}")) }
fn is_default_config_file_path(value: &str, file_name: &str) -> bool {
let normalized = value.trim().replace('\\', "/");
let normalized = normalized.trim_start_matches("./");
normalized == file_name
|| normalized.rsplit_once('/').is_some_and(|(dir, file)| dir == CONFIG_PATH && file == file_name)
}
pub fn is_blank_or_default_custom_stream_response_path(path: &Option<String>) -> bool {
path.as_ref()
.is_none_or(|value| value.trim().is_empty() || is_default_dir_path(value, DEFAULT_CUSTOM_STREAM_RESPONSE_PATH))
}
pub fn is_blank_or_default_mapping_path(path: &Option<String>) -> bool {
path.as_ref().is_none_or(|value| value.trim().is_empty() || is_default_config_file_path(value, MAPPING_FILE))
}
pub fn is_blank_or_default_template_path(path: &Option<String>) -> bool {
path.as_ref().is_none_or(|value| value.trim().is_empty() || is_default_config_file_path(value, TEMPLATE_FILE))
}
pub fn is_blank_or_default_storage_dir(path: &Option<String>) -> bool {
path.as_ref().is_none_or(|value| value.trim().is_empty() || is_default_dir_path(value, DEFAULT_STORAGE_DIR))
}
pub fn is_blank_or_default_backup_dir(path: &Option<String>) -> bool {
path.as_ref().is_none_or(|value| value.trim().is_empty() || is_default_dir_path(value, DEFAULT_BACKUP_DIR))
}
pub fn is_blank_or_default_user_config_dir(path: &Option<String>) -> bool {
path.as_ref().is_none_or(|value| value.trim().is_empty() || is_default_dir_path(value, DEFAULT_USER_CONFIG_DIR))
}
pub fn is_blank_or_default_user_file_path(path: &Option<String>) -> bool {
path.as_ref().is_none_or(|value| value.trim().is_empty() || is_default_config_file_path(value, USER_FILE))
}
pub fn normalize_optional_dir(path: &Option<String>, default_dir: &str) -> Option<String> {
path.as_ref().and_then(|value| {
if value.trim().is_empty() || is_default_dir_path(value, default_dir) {
None
} else {
Some(value.clone())
}
})
}
pub fn normalize_optional_config_file_path(path: &Option<String>, default_file_name: &str) -> Option<String> {
path.as_ref().and_then(|value| {
if value.trim().is_empty() || is_default_config_file_path(value, default_file_name) {
None
} else {
Some(value.clone())
}
})
}
pub fn is_none_or_empty_video(video: &Option<VideoConfigDto>) -> bool {
video.as_ref().is_none_or(VideoConfigDto::is_empty)
}
pub fn is_none_or_empty_metadata_update(metadata_update: &Option<MetadataUpdateConfigDto>) -> bool {
metadata_update.as_ref().is_none_or(MetadataUpdateConfigDto::is_empty)
}
//////////////////////////////
// HDHomerun Device Defaults
//////////////////////////////
+1
View File
@@ -8,6 +8,7 @@ use std::{borrow::Cow, sync::atomic::Ordering};
use url::Url;
pub const PROVIDER_SCHEME_PREFIX: &str = "provider://";
pub const BATCH_SCHEME_PREFIX: &str = "batch://";
pub const CONTENT_TYPE_JSON: &str = "application/json";
pub const CONTENT_TYPE_CBOR: &str = "application/cbor";