diff --git a/frontend/src-tauri/src/commands/backend.rs b/frontend/src-tauri/src/commands/backend.rs new file mode 100644 index 000000000..0149ccd78 --- /dev/null +++ b/frontend/src-tauri/src/commands/backend.rs @@ -0,0 +1,324 @@ +use tauri_plugin_shell::ShellExt; +use tauri::Manager; +use std::sync::Mutex; +use std::path::PathBuf; +use crate::utils::add_log; + +// Store backend process handle globally +static BACKEND_PROCESS: Mutex> = Mutex::new(None); +static BACKEND_STARTING: Mutex = Mutex::new(false); + +// Helper function to reset starting flag +fn reset_starting_flag() { + let mut starting_guard = BACKEND_STARTING.lock().unwrap(); + *starting_guard = false; +} + +// Check if backend is already running or starting +fn check_backend_status() -> Result<(), String> { + // Check if backend is already running + { + let process_guard = BACKEND_PROCESS.lock().unwrap(); + if process_guard.is_some() { + add_log("⚠️ Backend process already running, skipping start".to_string()); + return Err("Backend already running".to_string()); + } + } + + // Check and set starting flag to prevent multiple simultaneous starts + { + let mut starting_guard = BACKEND_STARTING.lock().unwrap(); + if *starting_guard { + add_log("⚠️ Backend already starting, skipping duplicate start".to_string()); + return Err("Backend startup already in progress".to_string()); + } + *starting_guard = true; + } + + Ok(()) +} + +// Find the bundled JRE and return the java executable path +fn find_bundled_jre(resource_dir: &PathBuf) -> Result { + let jre_dir = resource_dir.join("runtime").join("jre"); + let java_executable = if cfg!(windows) { + jre_dir.join("bin").join("java.exe") + } else { + jre_dir.join("bin").join("java") + }; + + if !java_executable.exists() { + let error_msg = format!("❌ Bundled JRE not found at: {:?}", java_executable); + add_log(error_msg.clone()); + return Err(error_msg); + } + + add_log(format!("✅ Found bundled JRE: {:?}", java_executable)); + Ok(java_executable) +} + +// Find the Stirling-PDF JAR file +fn find_stirling_jar(resource_dir: &PathBuf) -> Result { + let libs_dir = resource_dir.join("libs"); + let mut jar_files: Vec<_> = std::fs::read_dir(&libs_dir) + .map_err(|e| { + let error_msg = format!("Failed to read libs directory: {}. Make sure the JAR is copied to libs/", e); + add_log(error_msg.clone()); + error_msg + })? + .filter_map(|entry| entry.ok()) + .filter(|entry| { + let path = entry.path(); + // Match any .jar file containing "stirling-pdf" (case-insensitive) + path.extension().and_then(|s| s.to_str()).map(|ext| ext.eq_ignore_ascii_case("jar")).unwrap_or(false) + && path.file_name() + .and_then(|f| f.to_str()) + .map(|name| name.to_ascii_lowercase().contains("stirling-pdf")) + .unwrap_or(false) + }) + .collect(); + + if jar_files.is_empty() { + let error_msg = "No Stirling-PDF JAR found in libs directory.".to_string(); + add_log(error_msg.clone()); + return Err(error_msg); + } + + // Sort by filename to get the latest version (case-insensitive) + jar_files.sort_by(|a, b| { + let name_a = a.file_name().to_string_lossy().to_ascii_lowercase(); + let name_b = b.file_name().to_string_lossy().to_ascii_lowercase(); + name_b.cmp(&name_a) // Reverse order to get latest first + }); + + let jar_path = jar_files[0].path(); + add_log(format!("📋 Selected JAR: {:?}", jar_path.file_name().unwrap())); + Ok(jar_path) +} + +// Normalize path to remove Windows UNC prefix +fn normalize_path(path: &PathBuf) -> PathBuf { + if cfg!(windows) { + let path_str = path.to_string_lossy(); + if path_str.starts_with(r"\\?\") { + PathBuf::from(&path_str[4..]) // Remove \\?\ prefix + } else { + path.clone() + } + } else { + path.clone() + } +} + +// Create, configure and run the Java command to run Stirling-PDF JAR +fn run_stirling_pdf_jar(app: &tauri::AppHandle, java_path: &PathBuf, jar_path: &PathBuf) -> Result<(), String> { + // Configure logging to write outside src-tauri to prevent dev server restarts + let temp_dir = std::env::temp_dir(); + let log_dir = temp_dir.join("stirling-pdf-logs"); + std::fs::create_dir_all(&log_dir).ok(); // Create log directory if it doesn't exist + + // Define all Java options in an array + let log_path_option = format!("-Dlogging.file.path={}", log_dir.display()); + let java_options = vec![ + "-Xmx2g", + "-DBROWSER_OPEN=false", + "-DSTIRLING_PDF_DESKTOP_UI=false", + "-DSTIRLING_PDF_TAURI_MODE=true", + &log_path_option, + "-Dlogging.file.name=stirling-pdf.log", + "-jar", + jar_path.to_str().unwrap() + ]; + + // Log the equivalent command for external testing + let java_command = format!( + "TAURI_PARENT_PID={} && \"{}\" {}", + std::process::id(), + java_path.display(), + java_options.join(" ") + ); + add_log(format!("🔧 Equivalent command: {}", java_command)); + + let sidecar_command = app + .shell() + .command(java_path.to_str().unwrap()) + .args(java_options) + .env("TAURI_PARENT_PID", std::process::id().to_string()); + + add_log("⚙️ Starting backend with bundled JRE...".to_string()); + + let (rx, child) = sidecar_command + .spawn() + .map_err(|e| { + let error_msg = format!("❌ Failed to spawn sidecar: {}", e); + add_log(error_msg.clone()); + error_msg + })?; + + // Store the process handle + { + let mut process_guard = BACKEND_PROCESS.lock().unwrap(); + *process_guard = Some(child); + } + + add_log("✅ Backend started with bundled JRE, monitoring output...".to_string()); + + // Start monitoring output + monitor_backend_output(rx); + + Ok(()) +} + +// Monitor backend output in a separate task +fn monitor_backend_output(mut rx: tauri::async_runtime::Receiver) { + tokio::spawn(async move { + let mut _startup_detected = false; + let mut error_count = 0; + + while let Some(event) = rx.recv().await { + match event { + tauri_plugin_shell::process::CommandEvent::Stdout(output) => { + let output_str = String::from_utf8_lossy(&output); + add_log(format!("📤 Backend: {}", output_str)); + + // Look for startup indicators + if output_str.contains("Started SPDFApplication") || + output_str.contains("Navigate to "){ + _startup_detected = true; + add_log(format!("🎉 Backend startup detected: {}", output_str)); + } + + // Look for port binding + if output_str.contains("8080") { + add_log(format!("🔌 Port 8080 related output: {}", output_str)); + } + } + tauri_plugin_shell::process::CommandEvent::Stderr(output) => { + let output_str = String::from_utf8_lossy(&output); + add_log(format!("📥 Backend Error: {}", output_str)); + + // Look for error indicators + if output_str.contains("ERROR") || output_str.contains("Exception") || output_str.contains("FATAL") { + error_count += 1; + add_log(format!("⚠️ Backend error #{}: {}", error_count, output_str)); + } + + // Look for specific common issues + if output_str.contains("Address already in use") { + add_log("🚨 CRITICAL: Port 8080 is already in use by another process!".to_string()); + } + if output_str.contains("java.lang.ClassNotFoundException") { + add_log("🚨 CRITICAL: Missing Java dependencies!".to_string()); + } + if output_str.contains("java.io.FileNotFoundException") { + add_log("🚨 CRITICAL: Required file not found!".to_string()); + } + } + tauri_plugin_shell::process::CommandEvent::Error(error) => { + add_log(format!("❌ Backend process error: {}", error)); + } + tauri_plugin_shell::process::CommandEvent::Terminated(payload) => { + add_log(format!("💀 Backend terminated with code: {:?}", payload.code)); + if let Some(code) = payload.code { + match code { + 0 => println!("✅ Process terminated normally"), + 1 => println!("❌ Process terminated with generic error"), + 2 => println!("❌ Process terminated due to misuse"), + 126 => println!("❌ Command invoked cannot execute"), + 127 => println!("❌ Command not found"), + 128 => println!("❌ Invalid exit argument"), + 130 => println!("❌ Process terminated by Ctrl+C"), + _ => println!("❌ Process terminated with code: {}", code), + } + } + // Clear the stored process handle + let mut process_guard = BACKEND_PROCESS.lock().unwrap(); + *process_guard = None; + } + _ => { + println!("🔍 Unknown command event: {:?}", event); + } + } + } + + if error_count > 0 { + println!("⚠️ Backend process ended with {} errors detected", error_count); + } + }); +} + +// Command to start the backend with bundled JRE +#[tauri::command] +pub async fn start_backend(app: tauri::AppHandle) -> Result { + add_log("🚀 start_backend() called - Attempting to start backend with bundled JRE...".to_string()); + + // Check if backend is already running or starting + if let Err(msg) = check_backend_status() { + return Ok(msg); + } + + // Use Tauri's resource API to find the bundled JRE and JAR + let resource_dir = app.path().resource_dir().map_err(|e| { + let error_msg = format!("❌ Failed to get resource directory: {}", e); + add_log(error_msg.clone()); + reset_starting_flag(); + error_msg + })?; + + add_log(format!("🔍 Resource directory: {:?}", resource_dir)); + + // Find the bundled JRE + let java_executable = find_bundled_jre(&resource_dir).map_err(|e| { + reset_starting_flag(); + e + })?; + + // Find the Stirling-PDF JAR + let jar_path = find_stirling_jar(&resource_dir).map_err(|e| { + reset_starting_flag(); + e + })?; + + // Normalize the paths to remove Windows UNC prefix + let normalized_java_path = normalize_path(&java_executable); + let normalized_jar_path = normalize_path(&jar_path); + + add_log(format!("📦 Found JAR file: {:?}", jar_path)); + add_log(format!("📦 Normalized JAR path: {:?}", normalized_jar_path)); + add_log(format!("📦 Normalized Java path: {:?}", normalized_java_path)); + + // Create and start the Java command + run_stirling_pdf_jar(&app, &normalized_java_path, &normalized_jar_path).map_err(|e| { + reset_starting_flag(); + e + })?; + + // Wait for the backend to start + println!("⏳ Waiting for backend startup..."); + tokio::time::sleep(std::time::Duration::from_millis(10000)).await; + + // Reset the starting flag since startup is complete + reset_starting_flag(); + add_log("✅ Backend startup sequence completed, starting flag cleared".to_string()); + + Ok("Backend startup initiated successfully with bundled JRE".to_string()) +} + +// Cleanup function to stop backend on app exit +pub fn cleanup_backend() { + let mut process_guard = BACKEND_PROCESS.lock().unwrap(); + if let Some(child) = process_guard.take() { + let pid = child.pid(); + add_log(format!("🧹 App shutting down, cleaning up backend process (PID: {})", pid)); + + match child.kill() { + Ok(_) => { + add_log(format!("✅ Backend process (PID: {}) terminated during cleanup", pid)); + } + Err(e) => { + add_log(format!("❌ Failed to terminate backend process during cleanup: {}", e)); + println!("❌ Failed to terminate backend process during cleanup: {}", e); + } + } + } +} \ No newline at end of file diff --git a/frontend/src-tauri/src/commands/files.rs b/frontend/src-tauri/src/commands/files.rs new file mode 100644 index 000000000..797187795 --- /dev/null +++ b/frontend/src-tauri/src/commands/files.rs @@ -0,0 +1,80 @@ +use tauri::Manager; +use crate::utils::add_log; + +// Command to get opened file path (if app was launched with a file) +#[tauri::command] +pub async fn get_opened_file() -> Result, String> { + // Get command line arguments + let args: Vec = std::env::args().collect(); + + // Look for a PDF file argument (skip the first arg which is the executable) + for arg in args.iter().skip(1) { + if arg.ends_with(".pdf") && std::path::Path::new(arg).exists() { + add_log(format!("📂 PDF file opened: {}", arg)); + return Ok(Some(arg.clone())); + } + } + + Ok(None) +} + +// Command to check bundled runtime and JAR +#[tauri::command] +pub async fn check_jar_exists(app: tauri::AppHandle) -> Result { + println!("🔍 Checking for bundled JRE and JAR files..."); + + if let Ok(resource_dir) = app.path().resource_dir() { + let mut status_parts = Vec::new(); + + // Check bundled JRE + let jre_dir = resource_dir.join("runtime").join("jre"); + let java_executable = if cfg!(windows) { + jre_dir.join("bin").join("java.exe") + } else { + jre_dir.join("bin").join("java") + }; + + if java_executable.exists() { + status_parts.push("✅ Bundled JRE found".to_string()); + } else { + status_parts.push("❌ Bundled JRE not found".to_string()); + } + + // Check JAR files + let libs_dir = resource_dir.join("libs"); + if libs_dir.exists() { + match std::fs::read_dir(&libs_dir) { + Ok(entries) => { + let jar_files: Vec = entries + .filter_map(|entry| entry.ok()) + .filter(|entry| { + let path = entry.path(); + // Match any .jar file containing "stirling-pdf" (case-insensitive) + path.extension().and_then(|s| s.to_str()).map(|ext| ext.eq_ignore_ascii_case("jar")).unwrap_or(false) + && path.file_name() + .and_then(|f| f.to_str()) + .map(|name| name.to_ascii_lowercase().contains("stirling-pdf")) + .unwrap_or(false) + }) + .map(|entry| entry.file_name().to_string_lossy().to_string()) + .collect(); + + if !jar_files.is_empty() { + status_parts.push(format!("✅ Found JAR files: {:?}", jar_files)); + } else { + status_parts.push("❌ No Stirling-PDF JAR files found".to_string()); + } + } + Err(e) => { + status_parts.push(format!("❌ Failed to read libs directory: {}", e)); + } + } + } else { + status_parts.push("❌ Libs directory not found".to_string()); + } + + Ok(status_parts.join("\n")) + } else { + Ok("❌ Could not access bundled resources".to_string()) + } +} \ No newline at end of file diff --git a/frontend/src-tauri/src/commands/health.rs b/frontend/src-tauri/src/commands/health.rs new file mode 100644 index 000000000..a7b7f1b0f --- /dev/null +++ b/frontend/src-tauri/src/commands/health.rs @@ -0,0 +1,33 @@ +// Command to check if backend is healthy +#[tauri::command] +pub async fn check_backend_health() -> Result { + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .build() + .map_err(|e| format!("Failed to create HTTP client: {}", e))?; + + match client.get("http://localhost:8080/api/v1/info/status").send().await { + Ok(response) => { + let status = response.status(); + println!("💓 Health check response status: {}", status); + if status.is_success() { + match response.text().await { + Ok(_body) => { + Ok(true) + } + Err(e) => { + println!("⚠️ Failed to read health response: {}", e); + Ok(false) + } + } + } else { + println!("⚠️ Health check failed with status: {}", status); + Ok(false) + } + } + Err(e) => { + println!("❌ Health check error: {}", e); + Ok(false) + } + } +} \ No newline at end of file diff --git a/frontend/src-tauri/src/commands/mod.rs b/frontend/src-tauri/src/commands/mod.rs new file mode 100644 index 000000000..4c8d87851 --- /dev/null +++ b/frontend/src-tauri/src/commands/mod.rs @@ -0,0 +1,7 @@ +pub mod backend; +pub mod health; +pub mod files; + +pub use backend::{start_backend, cleanup_backend}; +pub use health::check_backend_health; +pub use files::{get_opened_file, check_jar_exists}; \ No newline at end of file diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 0f4142308..d1654a662 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -1,418 +1,10 @@ -use tauri_plugin_shell::ShellExt; -use tauri::Manager; use tauri::{RunEvent, WindowEvent}; -// Store backend process handle and logs globally -use std::sync::Mutex; -use std::collections::VecDeque; +mod utils; +mod commands; -static BACKEND_PROCESS: Mutex> = Mutex::new(None); -static BACKEND_LOGS: Mutex> = Mutex::new(VecDeque::new()); -static BACKEND_STARTING: Mutex = Mutex::new(false); - -// Helper function to add log entry -fn add_log(message: String) { - let mut logs = BACKEND_LOGS.lock().unwrap(); - logs.push_back(format!("{}: {}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(), message)); - // Keep only last 100 log entries - if logs.len() > 100 { - logs.pop_front(); - } - println!("{}", message); // Also print to console -} - -// Helper function to reset starting flag -fn reset_starting_flag() { - let mut starting_guard = BACKEND_STARTING.lock().unwrap(); - *starting_guard = false; -} - -// Command to start the backend with bundled JRE -#[tauri::command] -async fn start_backend(app: tauri::AppHandle) -> Result { - add_log("🚀 start_backend() called - Attempting to start backend with bundled JRE...".to_string()); - - // Check if backend is already running or starting - { - let process_guard = BACKEND_PROCESS.lock().unwrap(); - if process_guard.is_some() { - add_log("⚠️ Backend process already running, skipping start".to_string()); - return Ok("Backend already running".to_string()); - } - } - - // Check and set starting flag to prevent multiple simultaneous starts - { - let mut starting_guard = BACKEND_STARTING.lock().unwrap(); - if *starting_guard { - add_log("⚠️ Backend already starting, skipping duplicate start".to_string()); - return Ok("Backend startup already in progress".to_string()); - } - *starting_guard = true; - } - - // Use Tauri's resource API to find the bundled JRE and JAR - let resource_dir = app.path().resource_dir().map_err(|e| { - let error_msg = format!("❌ Failed to get resource directory: {}", e); - add_log(error_msg.clone()); - reset_starting_flag(); - error_msg - })?; - - add_log(format!("🔍 Resource directory: {:?}", resource_dir)); - - // Find the bundled JRE - let jre_dir = resource_dir.join("runtime").join("jre"); - let java_executable = if cfg!(windows) { - jre_dir.join("bin").join("java.exe") - } else { - jre_dir.join("bin").join("java") - }; - - if !java_executable.exists() { - let error_msg = format!("❌ Bundled JRE not found at: {:?}", java_executable); - add_log(error_msg.clone()); - reset_starting_flag(); - return Err(error_msg); - } - - add_log(format!("✅ Found bundled JRE: {:?}", java_executable)); - - // Find the Stirling-PDF JAR - let libs_dir = resource_dir.join("libs"); - let mut jar_files: Vec<_> = std::fs::read_dir(&libs_dir) - .map_err(|e| { - let error_msg = format!("Failed to read libs directory: {}. Make sure the JAR is copied to libs/", e); - add_log(error_msg.clone()); - reset_starting_flag(); - error_msg - })? - .filter_map(|entry| entry.ok()) - .filter(|entry| { - let path = entry.path(); - // Match any .jar file containing "stirling-pdf" (case-insensitive) - path.extension().and_then(|s| s.to_str()).map(|ext| ext.eq_ignore_ascii_case("jar")).unwrap_or(false) - && path.file_name() - .and_then(|f| f.to_str()) - .map(|name| name.to_ascii_lowercase().contains("stirling-pdf")) - .unwrap_or(false) - }) - .collect(); - - if jar_files.is_empty() { - let error_msg = "No Stirling-PDF JAR found in libs directory.".to_string(); - add_log(error_msg.clone()); - reset_starting_flag(); - return Err(error_msg); - } - - // Sort by filename to get the latest version (case-insensitive) - jar_files.sort_by(|a, b| { - let name_a = a.file_name().to_string_lossy().to_ascii_lowercase(); - let name_b = b.file_name().to_string_lossy().to_ascii_lowercase(); - name_b.cmp(&name_a) // Reverse order to get latest first - }); - - let jar_path = jar_files[0].path(); - add_log(format!("📋 Selected JAR: {:?}", jar_path.file_name().unwrap())); - - // Normalize the paths to remove Windows UNC prefix \\?\ - let normalized_java_path = if cfg!(windows) { - let path_str = java_executable.to_string_lossy(); - if path_str.starts_with(r"\\?\") { - std::path::PathBuf::from(&path_str[4..]) // Remove \\?\ prefix - } else { - java_executable.clone() - } - } else { - java_executable.clone() - }; - - let normalized_jar_path = if cfg!(windows) { - let path_str = jar_path.to_string_lossy(); - if path_str.starts_with(r"\\?\") { - std::path::PathBuf::from(&path_str[4..]) // Remove \\?\ prefix - } else { - jar_path.clone() - } - } else { - jar_path.clone() - }; - - add_log(format!("📦 Found JAR file: {:?}", jar_path)); - add_log(format!("📦 Normalized JAR path: {:?}", normalized_jar_path)); - add_log(format!("📦 Normalized Java path: {:?}", normalized_java_path)); - - // Log the equivalent command for external testing - let java_command = format!( - "\"{}\" -Xmx2g -DBROWSER_OPEN=false -DSTIRLING_PDF_DESKTOP_UI=false -jar \"{}\"", - normalized_java_path.display(), - normalized_jar_path.display() - ); - add_log(format!("🔧 Equivalent command: {}", java_command)); - - // Create Java command with bundled JRE using normalized paths - // Configure logging to write outside src-tauri to prevent dev server restarts - let temp_dir = std::env::temp_dir(); - let log_dir = temp_dir.join("stirling-pdf-logs"); - std::fs::create_dir_all(&log_dir).ok(); // Create log directory if it doesn't exist - - let sidecar_command = app - .shell() - .command(normalized_java_path.to_str().unwrap()) - .args([ - "-Xmx2g", - "-DBROWSER_OPEN=false", - "-DSTIRLING_PDF_DESKTOP_UI=false", - "-DSTIRLING_PDF_TAURI_MODE=true", - &format!("-Dlogging.file.path={}", log_dir.display()), - "-Dlogging.file.name=stirling-pdf.log", - "-jar", - normalized_jar_path.to_str().unwrap() - ]) - .env("TAURI_PARENT_PID", std::process::id().to_string()); - - add_log("⚙️ Starting backend with bundled JRE...".to_string()); - - let (mut rx, child) = sidecar_command - .spawn() - .map_err(|e| { - let error_msg = format!("❌ Failed to spawn sidecar: {}", e); - add_log(error_msg.clone()); - reset_starting_flag(); - error_msg - })?; - - // Store the process handle - { - let mut process_guard = BACKEND_PROCESS.lock().unwrap(); - *process_guard = Some(child); - } - - add_log("✅ Backend started with bundled JRE, monitoring output...".to_string()); - - // Listen to sidecar output for debugging - tokio::spawn(async move { - let mut _startup_detected = false; - let mut error_count = 0; - - while let Some(event) = rx.recv().await { - match event { - tauri_plugin_shell::process::CommandEvent::Stdout(output) => { - let output_str = String::from_utf8_lossy(&output); - add_log(format!("📤 Backend stdout: {}", output_str)); - - // Look for startup indicators - if output_str.contains("Started SPDFApplication") || - output_str.contains("Navigate to "){ - _startup_detected = true; - add_log(format!("🎉 Backend startup detected: {}", output_str)); - } - - // Look for port binding - if output_str.contains("8080") { - add_log(format!("🔌 Port 8080 related output: {}", output_str)); - } - } - tauri_plugin_shell::process::CommandEvent::Stderr(output) => { - let output_str = String::from_utf8_lossy(&output); - add_log(format!("📥 Backend stderr: {}", output_str)); - - // Look for error indicators - if output_str.contains("ERROR") || output_str.contains("Exception") || output_str.contains("FATAL") { - error_count += 1; - add_log(format!("⚠️ Backend error #{}: {}", error_count, output_str)); - } - - // Look for specific common issues - if output_str.contains("Address already in use") { - add_log("🚨 CRITICAL: Port 8080 is already in use by another process!".to_string()); - } - if output_str.contains("java.lang.ClassNotFoundException") { - add_log("🚨 CRITICAL: Missing Java dependencies!".to_string()); - } - if output_str.contains("java.io.FileNotFoundException") { - add_log("🚨 CRITICAL: Required file not found!".to_string()); - } - } - tauri_plugin_shell::process::CommandEvent::Error(error) => { - add_log(format!("❌ Backend process error: {}", error)); - } - tauri_plugin_shell::process::CommandEvent::Terminated(payload) => { - add_log(format!("💀 Backend terminated with code: {:?}", payload.code)); - if let Some(code) = payload.code { - match code { - 0 => println!("✅ Process terminated normally"), - 1 => println!("❌ Process terminated with generic error"), - 2 => println!("❌ Process terminated due to misuse"), - 126 => println!("❌ Command invoked cannot execute"), - 127 => println!("❌ Command not found"), - 128 => println!("❌ Invalid exit argument"), - 130 => println!("❌ Process terminated by Ctrl+C"), - _ => println!("❌ Process terminated with code: {}", code), - } - } - // Clear the stored process handle - let mut process_guard = BACKEND_PROCESS.lock().unwrap(); - *process_guard = None; - } - _ => { - println!("🔍 Unknown command event: {:?}", event); - } - } - } - - if error_count > 0 { - println!("⚠️ Backend process ended with {} errors detected", error_count); - } - }); - - // Wait for the backend to start - println!("⏳ Waiting for backend startup..."); - tokio::time::sleep(std::time::Duration::from_millis(10000)).await; - - // Reset the starting flag since startup is complete - reset_starting_flag(); - add_log("✅ Backend startup sequence completed, starting flag cleared".to_string()); - - Ok("Backend startup initiated successfully with bundled JRE".to_string()) -} - -// Command to check if backend is healthy -#[tauri::command] -async fn check_backend_health() -> Result { - let client = reqwest::Client::builder() - .timeout(std::time::Duration::from_secs(5)) - .build() - .map_err(|e| format!("Failed to create HTTP client: {}", e))?; - - match client.get("http://localhost:8080/api/v1/info/status").send().await { - Ok(response) => { - let status = response.status(); - println!("💓 Health check response status: {}", status); - if status.is_success() { - match response.text().await { - Ok(_body) => { - Ok(true) - } - Err(e) => { - println!("⚠️ Failed to read health response: {}", e); - Ok(false) - } - } - } else { - println!("⚠️ Health check failed with status: {}", status); - Ok(false) - } - } - Err(e) => { - println!("❌ Health check error: {}", e); - Ok(false) - } - } -} - - - -// Command to get opened file path (if app was launched with a file) -#[tauri::command] -async fn get_opened_file() -> Result, String> { - // Get command line arguments - let args: Vec = std::env::args().collect(); - - // Look for a PDF file argument (skip the first arg which is the executable) - for arg in args.iter().skip(1) { - if arg.ends_with(".pdf") && std::path::Path::new(arg).exists() { - add_log(format!("📂 PDF file opened: {}", arg)); - return Ok(Some(arg.clone())); - } - } - - Ok(None) -} - -// Command to check bundled runtime and JAR -#[tauri::command] -async fn check_jar_exists(app: tauri::AppHandle) -> Result { - println!("🔍 Checking for bundled JRE and JAR files..."); - - if let Ok(resource_dir) = app.path().resource_dir() { - let mut status_parts = Vec::new(); - - // Check bundled JRE - let jre_dir = resource_dir.join("runtime").join("jre"); - let java_executable = if cfg!(windows) { - jre_dir.join("bin").join("java.exe") - } else { - jre_dir.join("bin").join("java") - }; - - if java_executable.exists() { - status_parts.push("✅ Bundled JRE found".to_string()); - } else { - status_parts.push("❌ Bundled JRE not found".to_string()); - } - - // Check JAR files - let libs_dir = resource_dir.join("libs"); - if libs_dir.exists() { - match std::fs::read_dir(&libs_dir) { - Ok(entries) => { - let jar_files: Vec = entries - .filter_map(|entry| entry.ok()) - .filter(|entry| { - let path = entry.path(); - // Match any .jar file containing "stirling-pdf" (case-insensitive) - path.extension().and_then(|s| s.to_str()).map(|ext| ext.eq_ignore_ascii_case("jar")).unwrap_or(false) - && path.file_name() - .and_then(|f| f.to_str()) - .map(|name| name.to_ascii_lowercase().contains("stirling-pdf")) - .unwrap_or(false) - }) - .map(|entry| entry.file_name().to_string_lossy().to_string()) - .collect(); - - if !jar_files.is_empty() { - status_parts.push(format!("✅ Found JAR files: {:?}", jar_files)); - } else { - status_parts.push("❌ No Stirling-PDF JAR files found".to_string()); - } - } - Err(e) => { - status_parts.push(format!("❌ Failed to read libs directory: {}", e)); - } - } - } else { - status_parts.push("❌ Libs directory not found".to_string()); - } - - Ok(status_parts.join("\n")) - } else { - Ok("❌ Could not access bundled resources".to_string()) - } -} - - - - -// Cleanup function to stop backend on app exit -fn cleanup_backend() { - let mut process_guard = BACKEND_PROCESS.lock().unwrap(); - if let Some(child) = process_guard.take() { - let pid = child.pid(); - add_log(format!("🧹 App shutting down, cleaning up backend process (PID: {})", pid)); - - match child.kill() { - Ok(_) => { - add_log(format!("✅ Backend process (PID: {}) terminated during cleanup", pid)); - } - Err(e) => { - add_log(format!("❌ Failed to terminate backend process during cleanup: {}", e)); - println!("❌ Failed to terminate backend process during cleanup: {}", e); - } - } - } -} +use commands::{start_backend, check_backend_health, check_jar_exists, get_opened_file, cleanup_backend}; +use utils::add_log; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { @@ -439,4 +31,4 @@ pub fn run() { _ => {} } }); -} +} \ No newline at end of file diff --git a/frontend/src-tauri/src/utils/logging.rs b/frontend/src-tauri/src/utils/logging.rs new file mode 100644 index 000000000..d529a1a3f --- /dev/null +++ b/frontend/src-tauri/src/utils/logging.rs @@ -0,0 +1,16 @@ +use std::sync::Mutex; +use std::collections::VecDeque; + +// Store backend logs globally +static BACKEND_LOGS: Mutex> = Mutex::new(VecDeque::new()); + +// Helper function to add log entry +pub fn add_log(message: String) { + let mut logs = BACKEND_LOGS.lock().unwrap(); + logs.push_back(format!("{}: {}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(), message)); + // Keep only last 100 log entries + if logs.len() > 100 { + logs.pop_front(); + } + println!("{}", message); // Also print to console +} \ No newline at end of file diff --git a/frontend/src-tauri/src/utils/mod.rs b/frontend/src-tauri/src/utils/mod.rs new file mode 100644 index 000000000..4f7f3c38d --- /dev/null +++ b/frontend/src-tauri/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod logging; + +pub use logging::add_log; \ No newline at end of file