mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-20 23:02:34 +02:00
Feature/structured errors landing page (#705)
- structured errors - landing page - fix: table header background
This commit is contained in:
@@ -35,7 +35,7 @@ use indexmap::IndexMap;
|
||||
use log::{debug, error, info, log_enabled, warn, Level};
|
||||
use shared::{
|
||||
concat_string,
|
||||
error::{get_errors_notify_message, notify_err, TuliproxError},
|
||||
error::{get_errors_notify_message, TuliproxError},
|
||||
foundation::{get_field_value, set_field_value, Filter, ValueAccessor, ValueProvider},
|
||||
model::{
|
||||
ClusterSource, CounterModifier, FieldGetAccessor, FieldSetAccessor, InputStats, InputType, ItemField,
|
||||
@@ -596,7 +596,7 @@ async fn process_source(
|
||||
if playlist_groups.is_empty() {
|
||||
broadcast_step("Playlist download", &format!("Input '{}' playlist is empty", input.name));
|
||||
info!("Source is empty {input_name}");
|
||||
errors.push(notify_err!("Source is empty {input_name}"));
|
||||
errors.push(TuliproxError::RepositoryPlaylist(format!("Source is empty {input_name}")));
|
||||
} else {
|
||||
source_playlists.push(FetchedPlaylist { input, source: playlist_groups, epg: tvguide });
|
||||
log_memory_snapshot(
|
||||
@@ -621,10 +621,10 @@ async fn process_source(
|
||||
if source_downloaded {
|
||||
if source_playlists.is_empty() {
|
||||
debug!("Source at index {source_idx} is empty");
|
||||
errors.push(notify_err!(
|
||||
errors.push(TuliproxError::RepositoryPlaylist(format!(
|
||||
"Source at index {source_idx} is empty: {}",
|
||||
source.inputs.iter().map(Clone::clone).collect::<Vec<Arc<str>>>().join(", ")
|
||||
));
|
||||
)));
|
||||
} else {
|
||||
debug_if_enabled!(
|
||||
"Source has {} groups",
|
||||
@@ -1542,7 +1542,7 @@ pub async fn exec_processing(
|
||||
|
||||
// log errors
|
||||
for err in &errors {
|
||||
error!("{}", err.message);
|
||||
error!("{}", err.message());
|
||||
}
|
||||
|
||||
if !stats.is_empty() {
|
||||
|
||||
@@ -52,7 +52,7 @@ pub async fn update_generic_stream_metadata(
|
||||
|
||||
// Determine storage file path based on input type
|
||||
let storage_path = get_input_storage_path(&input.name, storage_dir).await
|
||||
.map_err(|e| shared::error::info_err!("Storage path error: {e}"))?;
|
||||
.map_err(|e| TuliproxError::Io(format!("Storage path error: {e}")))?;
|
||||
|
||||
let (db_path, storage_kind) = match input.input_type {
|
||||
InputType::M3u | InputType::M3uBatch => (
|
||||
@@ -82,7 +82,11 @@ pub async fn update_generic_stream_metadata(
|
||||
};
|
||||
|
||||
if !db_path.exists() {
|
||||
return Err(shared::error::info_err!("Playlist DB file not found for input {}: {}", input.name, db_path.display()));
|
||||
return Err(shared::error::TuliproxError::Config(format!(
|
||||
"Playlist DB file not found for input {input_name}: {db_path_display}",
|
||||
input_name = input.name,
|
||||
db_path_display = db_path.display()
|
||||
)));
|
||||
}
|
||||
|
||||
let needs_provider_connection = requires_provider_connection_for_generic_probe(input.input_type);
|
||||
@@ -98,7 +102,7 @@ pub async fn update_generic_stream_metadata(
|
||||
|
||||
if needs_provider_connection && active_handle.is_none() && acquired_handle.is_none() {
|
||||
warn!("Skipping probe for generic stream {unique_id} due to connection limits");
|
||||
return Err(shared::error::info_err!("No connection available"));
|
||||
return Err(TuliproxError::Probe(format!("Skipping probe for generic stream {unique_id} due to connection limits")));
|
||||
}
|
||||
|
||||
let probe_url = stream_url.to_string();
|
||||
@@ -143,7 +147,7 @@ pub async fn update_generic_stream_metadata(
|
||||
ProbeUrlOutcome::Success(_quality, raw_video, raw_audio, stats) => (raw_video, raw_audio, stats),
|
||||
ProbeUrlOutcome::Failed(ProbeFailureKind::NotFound) => {
|
||||
warn!("Probe target not found (404) for generic stream: {unique_id}");
|
||||
return Err(shared::error::info_err!("Probe target returned 404 Not Found for stream {unique_id}"));
|
||||
return Err(shared::error::TuliproxError::Probe(format!("Probe target returned 404 Not Found for stream {unique_id}")));
|
||||
}
|
||||
ProbeUrlOutcome::Failed(ProbeFailureKind::Other) => {
|
||||
warn!("Probe failed or timed out for generic stream: {unique_id}");
|
||||
@@ -246,8 +250,8 @@ pub async fn update_generic_stream_metadata(
|
||||
Ok(updated)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| shared::error::info_err!("Failed to join generic probe DB update task: {e}"))?
|
||||
.map_err(|e| shared::error::info_err!("{e}"))?;
|
||||
.map_err(|e| shared::error::TuliproxError::Config(format!("Failed to join generic probe DB update task: {e}")))?
|
||||
.map_err(shared::error::TuliproxError::Config)?;
|
||||
|
||||
drop(file_lock);
|
||||
if updated {
|
||||
|
||||
@@ -250,7 +250,7 @@ impl TraktCategoriesProcessor {
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Failed to fetch Trakt list {cache_key}: {}", err.message);
|
||||
warn!("Failed to fetch Trakt list {cache_key}: {}", err.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ pub async fn update_live_stream_metadata(
|
||||
) -> Result<Option<LiveStreamProperties>, TuliproxError> {
|
||||
let storage_dir = &app_config.config.load().storage_dir;
|
||||
let storage_path = get_input_storage_path(&input.name, storage_dir).await
|
||||
.map_err(|e| shared::error::info_err!("Storage path error: {e}"))?;
|
||||
.map_err(|e| TuliproxError::Io(format!("Storage path error: {e}")))?;
|
||||
|
||||
// Try to load existing info first to preserve data
|
||||
let mut props: Option<LiveStreamProperties> = None;
|
||||
@@ -175,16 +175,16 @@ pub async fn update_live_stream_metadata(
|
||||
if let Some(stream_id) = stream_id_opt {
|
||||
persist_input_live_info(app_config, &storage_path, XtreamCluster::Live, &input.name, stream_id, &properties)
|
||||
.await
|
||||
.map_err(|e| shared::error::info_err!("Persist error: {e}"))?;
|
||||
.map_err(|e| shared::error::TuliproxError::Io(format!("Persist error: {e}")))?;
|
||||
}
|
||||
}
|
||||
|
||||
if !success {
|
||||
if not_found {
|
||||
return Err(shared::error::info_err!("Probe failed with 404 Not Found for stream {display_id}"));
|
||||
return Err(shared::error::TuliproxError::Probe(format!("Probe failed with 404 Not Found for stream {display_id}")));
|
||||
}
|
||||
// Return error to propagate failure up to task manager/logs
|
||||
return Err(shared::error::info_err!("Probe failed for stream {display_id}"));
|
||||
return Err(shared::error::TuliproxError::Probe(format!("Probe failed for stream {display_id}")));
|
||||
}
|
||||
|
||||
Ok(Some(properties))
|
||||
|
||||
@@ -649,7 +649,7 @@ pub async fn update_series_metadata(
|
||||
let storage_dir = &app_config.config.load().storage_dir;
|
||||
let storage_path = get_input_storage_path(&input.name, storage_dir)
|
||||
.await
|
||||
.map_err(|e| shared::error::info_err!("Storage path error: {e}"))?;
|
||||
.map_err(|e| TuliproxError::Io(format!("Storage path error: {e}")))?;
|
||||
|
||||
if input.has_flag(ConfigInputFlags::XtreamSkipSeries) {
|
||||
return Ok(None);
|
||||
@@ -734,12 +734,12 @@ pub async fn update_series_metadata(
|
||||
if let Some(series_id) = series_id_opt {
|
||||
// Fetch Info from Provider
|
||||
let info_url = xtream::get_xtream_player_api_info_url(input, XtreamCluster::Series, series_id)
|
||||
.ok_or_else(|| shared::error::info_err!("Failed to build info URL"))?;
|
||||
.ok_or_else(|| shared::error::TuliproxError::Config("Failed to build info URL".to_string()))?;
|
||||
|
||||
let input_source = InputSource::from(input).with_url(info_url);
|
||||
let content = xtream::get_xtream_stream_info_content(app_config, client, &input_source, true)
|
||||
.await
|
||||
.map_err(|e| shared::error::info_err!("{e}"))?;
|
||||
.map_err(|e| shared::error::TuliproxError::Config(format!("{e}")))?;
|
||||
|
||||
if content.is_empty() {
|
||||
debug!("Series {display_id}: provider returned empty content for info fetch");
|
||||
@@ -763,16 +763,16 @@ pub async fn update_series_metadata(
|
||||
properties_updated = true;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(shared::error::info_err!(
|
||||
return Err(shared::error::TuliproxError::Config(format!(
|
||||
"Series {display_id}: failed to parse XtreamSeriesInfo: {e}. Raw response: {content}"
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(shared::error::info_err!(
|
||||
return Err(shared::error::TuliproxError::Config(format!(
|
||||
"Series {display_id}: failed to parse provider response as JSON: {e}. Raw response: {content}"
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -789,12 +789,12 @@ pub async fn update_series_metadata(
|
||||
};
|
||||
props = Some(new_props);
|
||||
} else {
|
||||
return Err(shared::error::info_err!("No Series properties available and no title found for {display_id}"));
|
||||
return Err(shared::error::TuliproxError::Config(format!("No Series properties available and no title found for {display_id}")));
|
||||
}
|
||||
}
|
||||
|
||||
let mut properties = props.ok_or_else(|| {
|
||||
shared::error::info_err!("No Series properties available after fallback creation for {display_id}")
|
||||
shared::error::TuliproxError::Config(format!("No Series properties available after fallback creation for {display_id}"))
|
||||
})?;
|
||||
|
||||
let resolve_tmdb_enabled = input.has_flag(ConfigInputFlags::ResolveTmdb);
|
||||
@@ -994,10 +994,10 @@ pub async fn update_series_metadata(
|
||||
if let Some(kind) = probe_failure {
|
||||
let err = match kind {
|
||||
ProbeFailureKind::NotFound => {
|
||||
shared::error::info_err!("Probe failed with 404 Not Found for Series {display_id}")
|
||||
shared::error::TuliproxError::Config(format!("Probe failed with 404 Not Found for Series {display_id}"))
|
||||
}
|
||||
ProbeFailureKind::Other => shared::error::info_err!("Probe failed for Series {display_id}"),
|
||||
ProbeFailureKind::Cancelled => shared::error::info_err!("Probe cancelled for Series {display_id}"),
|
||||
ProbeFailureKind::Other => shared::error::TuliproxError::Config(format!("Probe failed for Series {display_id}")),
|
||||
ProbeFailureKind::Cancelled => shared::error::TuliproxError::Config(format!("Probe cancelled for Series {display_id}")),
|
||||
};
|
||||
return Err(err);
|
||||
}
|
||||
@@ -1016,7 +1016,7 @@ pub async fn update_series_metadata(
|
||||
&properties,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| shared::error::info_err!("Persist error: {e}"))?;
|
||||
.map_err(|e| shared::error::TuliproxError::Config(format!("Persist error: {e}")))?;
|
||||
|
||||
debug_if_enabled!("Successfully updated Series metadata for ID {}", series_id);
|
||||
}
|
||||
|
||||
@@ -625,7 +625,7 @@ pub async fn update_vod_metadata(
|
||||
if should_fetch_info {
|
||||
if let Some(stream_id) = stream_id_opt {
|
||||
let info_url = xtream::get_xtream_player_api_info_url(input, XtreamCluster::Video, stream_id)
|
||||
.ok_or_else(|| shared::error::info_err!("Failed to build info URL"))?;
|
||||
.ok_or_else(|| shared::error::TuliproxError::Config("Failed to build info URL".to_string()))?;
|
||||
|
||||
let input_source = InputSource::from(input).with_url(info_url);
|
||||
|
||||
@@ -670,12 +670,12 @@ pub async fn update_vod_metadata(
|
||||
props = Some(new_props);
|
||||
} else {
|
||||
// We can't proceed without at least a name
|
||||
return Err(shared::error::info_err!("No VOD properties available and no title found for {display_id}"));
|
||||
return Err(shared::error::TuliproxError::Config(format!("No VOD properties available and no title found for {display_id}")));
|
||||
}
|
||||
}
|
||||
|
||||
let Some(mut properties) = props else {
|
||||
return Err(shared::error::info_err!("No VOD properties available after fallback creation for {display_id}"));
|
||||
return Err(shared::error::TuliproxError::Config(format!("No VOD properties available after fallback creation for {display_id}")));
|
||||
};
|
||||
|
||||
let resolve_tmdb_enabled = input.has_flag(ConfigInputFlags::ResolveTmdb);
|
||||
@@ -908,10 +908,10 @@ pub async fn update_vod_metadata(
|
||||
if let Some(kind) = probe_failure {
|
||||
let err = match kind {
|
||||
ProbeFailureKind::NotFound => {
|
||||
shared::error::info_err!("Probe failed with 404 Not Found for VOD {display_id}")
|
||||
shared::error::TuliproxError::Config(format!("Probe failed with 404 Not Found for VOD {display_id}"))
|
||||
}
|
||||
ProbeFailureKind::Other => shared::error::info_err!("Probe failed for VOD {display_id}"),
|
||||
ProbeFailureKind::Cancelled => shared::error::info_err!("Probe cancelled for VOD {display_id}"),
|
||||
ProbeFailureKind::Other => shared::error::TuliproxError::Config(format!("Probe failed for VOD {display_id}")),
|
||||
ProbeFailureKind::Cancelled => shared::error::TuliproxError::Config(format!("Probe cancelled for VOD {display_id}")),
|
||||
};
|
||||
return Err(err);
|
||||
}
|
||||
@@ -930,7 +930,7 @@ pub async fn update_vod_metadata(
|
||||
&properties,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| shared::error::info_err!("Persist error: {e}"))?;
|
||||
.map_err(|e| shared::error::TuliproxError::Config(format!("Persist error: {e}")))?;
|
||||
}
|
||||
|
||||
debug_if_enabled!("Successfully updated VOD metadata for '{}' (ID: {})", display_title, display_id);
|
||||
|
||||
Reference in New Issue
Block a user