mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-07-27 23:55:21 +00:00
Split up large tauri rust backend into files and functions
This commit is contained in:
parent
f6bcd83b72
commit
d7c670885a
324
frontend/src-tauri/src/commands/backend.rs
Normal file
324
frontend/src-tauri/src/commands/backend.rs
Normal file
@ -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<Option<tauri_plugin_shell::process::CommandChild>> = Mutex::new(None);
|
||||||
|
static BACKEND_STARTING: Mutex<bool> = 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<PathBuf, String> {
|
||||||
|
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<PathBuf, String> {
|
||||||
|
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<tauri_plugin_shell::process::CommandEvent>) {
|
||||||
|
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<String, String> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
frontend/src-tauri/src/commands/files.rs
Normal file
80
frontend/src-tauri/src/commands/files.rs
Normal file
@ -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<Option<String>, String> {
|
||||||
|
// Get command line arguments
|
||||||
|
let args: Vec<String> = 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<String, String> {
|
||||||
|
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<String> = 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())
|
||||||
|
}
|
||||||
|
}
|
33
frontend/src-tauri/src/commands/health.rs
Normal file
33
frontend/src-tauri/src/commands/health.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Command to check if backend is healthy
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn check_backend_health() -> Result<bool, String> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
frontend/src-tauri/src/commands/mod.rs
Normal file
7
frontend/src-tauri/src/commands/mod.rs
Normal file
@ -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};
|
@ -1,418 +1,10 @@
|
|||||||
use tauri_plugin_shell::ShellExt;
|
|
||||||
use tauri::Manager;
|
|
||||||
use tauri::{RunEvent, WindowEvent};
|
use tauri::{RunEvent, WindowEvent};
|
||||||
|
|
||||||
// Store backend process handle and logs globally
|
mod utils;
|
||||||
use std::sync::Mutex;
|
mod commands;
|
||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
static BACKEND_PROCESS: Mutex<Option<tauri_plugin_shell::process::CommandChild>> = Mutex::new(None);
|
use commands::{start_backend, check_backend_health, check_jar_exists, get_opened_file, cleanup_backend};
|
||||||
static BACKEND_LOGS: Mutex<VecDeque<String>> = Mutex::new(VecDeque::new());
|
use utils::add_log;
|
||||||
static BACKEND_STARTING: Mutex<bool> = 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<String, String> {
|
|
||||||
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<bool, String> {
|
|
||||||
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<Option<String>, String> {
|
|
||||||
// Get command line arguments
|
|
||||||
let args: Vec<String> = 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<String, String> {
|
|
||||||
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<String> = 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
@ -439,4 +31,4 @@ pub fn run() {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
16
frontend/src-tauri/src/utils/logging.rs
Normal file
16
frontend/src-tauri/src/utils/logging.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use std::sync::Mutex;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
// Store backend logs globally
|
||||||
|
static BACKEND_LOGS: Mutex<VecDeque<String>> = 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
|
||||||
|
}
|
3
frontend/src-tauri/src/utils/mod.rs
Normal file
3
frontend/src-tauri/src/utils/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod logging;
|
||||||
|
|
||||||
|
pub use logging::add_log;
|
Loading…
x
Reference in New Issue
Block a user