Fix/hls rewrite css fixes (#685)

* hls uri rewrite fixed
* source editor canvas pan delete icon fix
* css-fixes
* added epg smart match form to input
* staged input fix
* Disable wasm compression
This commit is contained in:
euzu
2026-03-31 14:28:43 +02:00
committed by GitHub
parent 933a51040d
commit c82169542f
50 changed files with 905 additions and 179 deletions
+78 -8
View File
@@ -498,11 +498,8 @@ async fn playlist_download_from_input(
let mut save_status = false;
if hybrid {
if needs_m3u_download {
input_cache::update_cluster_status(
&mut status,
"default",
if m3u_error_count == 0 { ClusterState::Ok } else { ClusterState::Failed },
);
let m3u_state = if m3u_error_count == 0 { ClusterState::Ok } else { ClusterState::Failed };
input_cache::update_cluster_status(&mut status, "default", m3u_state);
save_status = true;
}
@@ -528,6 +525,9 @@ async fn playlist_download_from_input(
input_cache::update_cluster_status(&mut status, &cluster.to_string(), ClusterState::Failed);
}
save_status = !xtream_clusters_to_download.is_empty();
} else {
input_cache::update_cluster_status(&mut status, "default", ClusterState::Failed);
save_status = true;
}
if save_status {
@@ -687,6 +687,30 @@ async fn download_input_epg(
tvguide
}
/// `invalidate_input_cache_status` performs a non-atomic file I/O sequence
/// (`input_cache::load_input_status` + `input_cache::save_input_status`).
/// Call this only while holding the per-input lock from
/// `PlaylistProcessingContext::get_input_lock` (as done in `download_input`).
async fn invalidate_input_cache_status(ctx: &PlaylistProcessingContext, input: &ConfigInput) {
let storage_dir = { ctx.config.config.load().storage_dir.clone() };
let storage_path = input_cache::resolve_input_storage_path(&storage_dir, &input.name).await;
let mut status = input_cache::load_input_status(&storage_path);
if !status.clusters.is_empty() {
status.clusters.clear();
input_cache::save_input_status(&storage_path, &status);
}
}
async fn load_cached_input_playlist(
ctx: &PlaylistProcessingContext,
input: &Arc<ConfigInput>,
) -> (Box<dyn PlaylistSource>, Option<TuliproxError>) {
match load_input_playlist(ctx, input, None).await {
Ok(pl_source) => (pl_source, None),
Err(err) => (MemoryPlaylistSource::default().boxed(), Some(err)),
}
}
async fn download_input(
ctx: &PlaylistProcessingContext,
input: &Arc<ConfigInput>,
@@ -695,10 +719,10 @@ async fn download_input(
let need_download = !ctx.is_input_downloaded(&input.name).await;
// Keep this lock for the whole critical section (download + persist/load + mark processed)
// so parallel sources sharing the same input cannot observe a half-written state.
let _input_lock = if need_download { Some(ctx.get_input_lock(&input.name).await) } else { None };
let mut input_lock = if need_download { Some(ctx.get_input_lock(&input.name).await) } else { None };
let mut mark_as_processed = false;
let playlist_download_result = if need_download {
let mut playlist_download_result = if need_download {
// Check again after lock
let already_processed = ctx.is_input_downloaded(&input.name).await;
@@ -718,7 +742,50 @@ async fn download_input(
PlaylistDownloadResult::new(vec![], vec![], true, false)
};
let (playlist, error) = if playlist_download_result.was_cached || playlist_download_result.persisted {
let mut preloaded_playlist: Option<(Box<dyn PlaylistSource>, Option<TuliproxError>)> = None;
if playlist_download_result.was_cached {
let (cached_playlist, cached_error) = load_cached_input_playlist(ctx, input).await;
// Defensive fallback: if cache metadata says "valid" but persisted data is unreadable,
// retry once before forcing a refresh.
let must_force_refresh = cached_error.is_some();
if must_force_refresh {
warn!(
"Input '{}' cache hit produced unreadable playlist; retrying cached load once",
input.name
);
let (retry_playlist, retry_error) = load_cached_input_playlist(ctx, input).await;
if retry_error.is_none() {
preloaded_playlist = Some((retry_playlist, None));
} else {
if input_lock.is_none() {
input_lock = Some(ctx.get_input_lock(&input.name).await);
}
// Re-check immediately after locking to avoid duplicate refreshes when another worker
// repaired the cache between our earlier retry and lock acquisition.
let (locked_retry_playlist, locked_retry_error) = load_cached_input_playlist(ctx, input).await;
if locked_retry_error.is_none() {
warn!(
"Input '{}' cache became readable after lock re-check; skipping refresh",
input.name
);
preloaded_playlist = Some((locked_retry_playlist, None));
} else {
warn!(
"Input '{}' cached playlist remained unreadable after retry and lock re-check; invalidating cache and forcing refresh",
input.name
);
invalidate_input_cache_status(ctx, input).await;
playlist_download_result = playlist_download_from_input(&ctx.client, &ctx.config, input).await;
}
}
} else {
preloaded_playlist = Some((cached_playlist, None));
}
}
let (playlist, error) = if let Some(preloaded) = preloaded_playlist {
preloaded
} else if playlist_download_result.was_cached || playlist_download_result.persisted {
match load_input_playlist(ctx, input, None).await {
Ok(pl_source) => (pl_source, None),
Err(e) => (MemoryPlaylistSource::default().boxed(), Some(e)),
@@ -736,6 +803,9 @@ async fn download_input(
ctx.mark_input_downloaded(input.name.clone()).await;
}
// Explicitly release per-input lock after load/persist/mark steps are completed.
drop(input_lock);
(playlist_download_result.download_err, playlist, error)
}