Fix/catchup (#778)

* Fix: setting auth web_ui token_ttl_mins = 0 fixed.
* Fix: catchup
* New: added playlist update log view
* Fix: Landing Page
New: "Last Page"  added for Landing Page selection
This commit is contained in:
euzu
2026-06-18 16:03:09 +02:00
committed by GitHub
parent 026aa1adb8
commit bfcd66ffcc
49 changed files with 1757 additions and 295 deletions
+18 -4
View File
@@ -167,8 +167,8 @@ pub struct WebUiConfigDto {
pub kick_secs: u64,
#[serde(default, skip_serializing_if = "is_false")]
pub combine_views_stats_streams: bool,
#[serde(default, skip_serializing_if = "ViewType::is_default")]
pub landing_page: ViewType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub landing_page: Option<ViewType>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stream_info: Option<StreamInfoConfigDto>,
}
@@ -184,7 +184,7 @@ impl Default for WebUiConfigDto {
player_server: None,
kick_secs: default_kick_secs(),
combine_views_stats_streams: false,
landing_page: ViewType::default(),
landing_page: None,
stream_info: None,
}
}
@@ -196,7 +196,7 @@ impl WebUiConfigDto {
self.enabled == empty.enabled
&& self.user_ui_enabled == empty.user_ui_enabled
&& !self.combine_views_stats_streams
&& self.landing_page == ViewType::default()
&& self.landing_page.is_none()
&& is_blank_or_default_web_ui_path(&self.path)
&& is_blank_optional_str(self.player_server.as_deref())
&& self.kick_secs == default_kick_secs()
@@ -302,4 +302,18 @@ mod tests {
assert!(dto.stream_info.is_none());
assert!(dto.is_empty());
}
#[test]
fn web_ui_config_dto_default_uses_last_page_landing_page() {
let dto = WebUiConfigDto::default();
assert_eq!(dto.landing_page, None);
}
#[test]
fn web_ui_config_dto_is_not_empty_when_explicit_landing_page_is_set() {
let dto = WebUiConfigDto { landing_page: Some(ViewType::Streams), ..WebUiConfigDto::default() };
assert!(!dto.is_empty());
}
}
+2 -1
View File
@@ -19,6 +19,7 @@ mod playlist_document;
mod playlist_info_document;
mod playlist_request;
mod processing_order;
mod progress;
mod regex_cache;
mod search_request;
mod short_epg;
@@ -45,7 +46,7 @@ pub use self::{
active_user_connection_change::*, auth::*, cluster_flags::*, config::*, download::*, epg::*, epg_request::*,
ip_check::*, item_field::*, library_request::*, mapping::*, media_properties::*, messaging::*, pagination::*,
playlist::*, playlist_categories::*, playlist_info_document::*, playlist_request::*, processing_order::*,
regex_cache::*, search_request::*, short_epg::*, stats::*, status_check::*, stream_history::*,
progress::*, regex_cache::*, search_request::*, short_epg::*, stats::*, status_check::*, stream_history::*,
stream_history_record::*, stream_info::*, stream_meter::*, stream_properties::*, strm_export_style::*,
system_info::*, target_type::*, transfer::*, ui_playlist_item::*, user_command::*, uuidtype::*, web_socket::*,
xtream::*,
+18
View File
@@ -0,0 +1,18 @@
use crate::model::LibraryScanSummary;
use serde::{Deserialize, Serialize};
/// Marker response for accepted long-running operations. Carries no payload:
/// progress is delivered over the event channel, not through this response.
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct OperationRunAccepted {}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct PlaylistUpdateProgressEvent {
pub target: String,
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct LibraryScanProgressEvent {
pub summary: LibraryScanSummary,
}
+4 -3
View File
@@ -1,6 +1,7 @@
use crate::model::{
user_command::UserCommand, ActiveUserConnectionChange, ConfigType, DownloadsDelta, DownloadsResponse,
LibraryScanSummary, PermissionSet, PlaylistUpdateState, StatusCheck, StreamMeterEntry, SystemInfo,
LibraryScanProgressEvent, PermissionSet, PlaylistUpdateProgressEvent, PlaylistUpdateState, StatusCheck,
StreamMeterEntry, SystemInfo,
};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
@@ -92,10 +93,10 @@ pub enum ProtocolMessage {
ActiveProviderCountResponse(usize),
ConfigChangeResponse(ConfigType),
PlaylistUpdateResponse(PlaylistUpdateState),
PlaylistUpdateProgressResponse(String, String),
PlaylistUpdateProgressResponse(PlaylistUpdateProgressEvent),
UserActionResponse(bool),
SystemInfoResponse(SystemInfo),
LibraryScanProgressResponse(LibraryScanSummary),
LibraryScanProgressResponse(LibraryScanProgressEvent),
StreamMeterBatchResponse(Vec<StreamMeterEntry>),
DownloadsResponse(DownloadsResponse),
DownloadsDeltaResponse(DownloadsDelta),
+1 -1
View File
@@ -135,7 +135,7 @@ pub static CONSTANTS: LazyLock<Constants> = LazyLock::new(|| {
re_ipv4: Regex::new(r"\b((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b").unwrap(),
re_ipv6: Regex::new(r"\b([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b|\b::([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}\b|\b([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}\b|\b::ffff:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b").unwrap(),
re_stream_url: Regex::new(
r"(?i)^(?P<scheme>(?:https?|batch|provider)://)[^/]+/(?P<ctx>live|video|movie|series|m3u-stream|resource)/[^/]+/[^/]+/"
r"(?i)^(?P<scheme>(?:https?|batch|provider)://)[^/]+/(?P<ctx>live|video|movie|series|m3u-stream|resource|timeshift|streaming|xtream|timeshift\.php)/[^/]+/[^/]+/"
)
.unwrap(),
re_url: Regex::new(r"(.*://).*?/(.*)").unwrap(),
+35
View File
@@ -197,4 +197,39 @@ mod tests {
set_sanitize_sensitive_info(previous);
}
#[test]
fn sanitize_sensitive_info_masks_path_credentials_for_all_xtream_contexts() {
// Regression: only `live|video|movie|series|m3u-stream|resource` were originally
// listed in `re_stream_url`, so URLs like `/timeshift/{user}/{pass}/...` leaked
// the username and password into log lines. The regex was extended to also
// cover `timeshift`, `streaming`, `xtream`, and `timeshift.php`.
let previous = super::is_sanitize_sensitive_info_enabled();
set_sanitize_sensitive_info(true);
let cases: &[(&str, &str)] = &[
(
"http://example/timeshift/myuser/mypass/3/2026-06-17:14-00/449.ts",
"http://***/timeshift/***/3/2026-06-17:14-00/449.ts",
),
("http://example/streaming/myuser/mypass/123.ts", "http://***/streaming/***/123.ts"),
("http://example/xtream/myuser/mypass/123.ts", "http://***/xtream/***/123.ts"),
("http://example/timeshift.php/myuser/mypass/123.ts", "http://***/timeshift.php/***/123.ts"),
("https://provider.example/movie/myuser/mypass/456.ts", "https://***/movie/***/456.ts"),
("https://provider.example/series/myuser/mypass/789.ts", "https://***/series/***/789.ts"),
];
for (input, expected) in cases {
assert_eq!(sanitize_sensitive_info(input).as_ref(), *expected, "input: {input}");
}
// Query-string credentials are masked by `re_credentials`, not by the
// stream-URL rewriter — the host is still masked by `re_url`.
assert_eq!(
sanitize_sensitive_info("http://example/player_api.php?username=foo&password=bar"),
"http://***/player_api.php?username=***&password=***"
);
set_sanitize_sensitive_info(previous);
}
}