diff --git a/packages/app-lib/src/api/logs/crash_analysis.rs b/packages/app-lib/src/api/logs/crash_analysis.rs index 251b90e..7d20b9d 100644 --- a/packages/app-lib/src/api/logs/crash_analysis.rs +++ b/packages/app-lib/src/api/logs/crash_analysis.rs @@ -109,6 +109,7 @@ pub struct CrashAnalysisMod { struct SourceCandidate { path: PathBuf, filename: String, + upload_name: String, source_type: &'static str, modified: SystemTime, } @@ -116,10 +117,25 @@ struct SourceCandidate { #[derive(Debug)] struct SourceText { filename: String, + upload_name: String, source_type: &'static str, modified: SystemTime, content: String, } +pub(super) struct CollectedCrashLog { + pub name: String, + pub content: CensoredString, +} + +pub(super) struct CollectedCrashLogs { + pub combined_log: CensoredString, + pub sources: Vec, +} + +struct CrashContext { + sources: Vec, + credentials: Vec, +} struct Rule { id: &'static str, @@ -1185,20 +1201,25 @@ async fn collect_candidates( ) -> crate::Result> { let mut candidates = Vec::new(); let mut locations = vec![ - (directories.game_logs_dir(instance_root), "minecraft_log"), + ( + directories.game_logs_dir(instance_root), + "minecraft_log", + None, + ), ( directories.game_crash_reports_dir(instance_root), "crash_report", + Some("crash-reports"), ), - (instance_root.to_path_buf(), "instance_log"), + (instance_root.to_path_buf(), "instance_log", None), ]; if let Some(dot_minecraft) = isolated_minecraft_root(instance_root) { // PCL-CE also searches the shared `.minecraft` root for JVM crash // logs when a version-isolated instance is active. - locations.push((dot_minecraft, "minecraft_root_log")); + locations.push((dot_minecraft, "minecraft_root_log", None)); } - for (directory, default_type) in locations { + for (directory, default_type, upload_prefix) in locations { if !tokio::fs::try_exists(&directory).await? { continue; } @@ -1248,6 +1269,9 @@ async fn collect_candidates( }; candidates.push(SourceCandidate { path: entry.path(), + upload_name: upload_prefix + .map(|prefix| format!("{prefix}/{filename}")) + .unwrap_or_else(|| filename.clone()), filename, source_type, modified: metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH), @@ -1257,6 +1281,52 @@ async fn collect_candidates( Ok(candidates) } +async fn collect_crash_context( + instance_id: &str, +) -> crate::Result { + let state = State::get().await?; + let (instance_path, game_dir_override) = + resolve_instance_path(instance_id, &state).await?; + let instance_root = state + .directories + .resolve_game_dir(&instance_path, game_dir_override.as_deref()); + let candidates = + collect_candidates(&instance_root, &state.directories).await?; + let selected = select_run_candidates(candidates); + let sources = read_sources(selected).await; + let credentials = Credentials::get_all(&state.pool) + .await? + .into_iter() + .collect::>(); + Ok(CrashContext { + sources, + credentials, + }) +} + +pub(super) async fn collect_crash_logs( + instance_id: &str, +) -> crate::Result { + let CrashContext { + sources, + credentials, + .. + } = collect_crash_context(instance_id).await?; + let combined_log = + CensoredString::censor(build_combined_log(&sources), &credentials); + let sources = sources + .into_iter() + .map(|source| CollectedCrashLog { + name: source.upload_name, + content: CensoredString::censor(source.content, &credentials), + }) + .collect(); + Ok(CollectedCrashLogs { + combined_log, + sources, + }) +} + fn isolated_minecraft_root(instance_root: &Path) -> Option { let versions_dir = instance_root.parent()?; if versions_dir @@ -1320,6 +1390,7 @@ async fn read_sources(candidates: Vec) -> Vec { }; sources.push(SourceText { filename: candidate.filename, + upload_name: candidate.upload_name, source_type: candidate.source_type, modified: candidate.modified, content: String::from_utf8_lossy(bytes).into_owned(), @@ -1779,6 +1850,7 @@ mod tests { fn source(filename: &str, content: &str) -> SourceText { SourceText { filename: filename.to_string(), + upload_name: filename.to_string(), source_type: "minecraft_log", modified: SystemTime::now(), content: content.to_string(), @@ -1802,6 +1874,7 @@ mod tests { let candidates = vec![SourceCandidate { path: PathBuf::from("launcher_log.txt"), filename: "launcher_log.txt".to_string(), + upload_name: "launcher_log.txt".to_string(), source_type: "minecraft_log", modified: SystemTime::now() - Duration::from_secs(181), }]; diff --git a/packages/app-lib/src/api/logs/logshare.rs b/packages/app-lib/src/api/logs/logshare.rs index 19b43d4..eba4654 100644 --- a/packages/app-lib/src/api/logs/logshare.rs +++ b/packages/app-lib/src/api/logs/logshare.rs @@ -4,8 +4,7 @@ use futures::StreamExt; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; -use super::CensoredString; -use super::crash_analysis::analyze_crash; +use super::crash_analysis::collect_crash_logs; use crate::State; use crate::emit_logshare_ai_event; @@ -200,32 +199,6 @@ fn source_identifier() -> String { format!("{LOGSHARE_SOURCE_PREFIX}/{}", env!("CARGO_PKG_VERSION")) } -struct CollectedCrashLog { - name: String, - content: CensoredString, -} - -struct CollectedCrashLogs { - combined_log: CensoredString, - sources: Vec, -} - -async fn collect_crash_logs( - instance_id: &str, -) -> crate::Result { - let analysis = analyze_crash(instance_id).await?; - Ok(CollectedCrashLogs { - combined_log: analysis.combined_log, - sources: analysis - .sources - .into_iter() - .map(|source| CollectedCrashLog { - name: source.filename, - content: source.content, - }) - .collect(), - }) -} async fn prepare_upload(instance_id: &str) -> crate::Result { let settings = get_log_share_settings().await?; let logs = collect_crash_logs(instance_id).await?;