mirror of
https://github.com/kennethreitz/kjvstudy.org.git
synced 2026-06-05 14:50:17 +00:00
Fix Tauri app to start server before window, use WebviewUrl::External
- Start FastAPI server before Tauri initializes - Wait for health check to pass before creating window - Create window programmatically with External URL - Try uv first, fall back to python3 - Remove window config from tauri.conf.json (now in code) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+74
-88
@@ -1,102 +1,78 @@
|
||||
// Prevents additional console window on Windows in release
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use std::process::{Child, Command};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Manager, RunEvent};
|
||||
use tauri::{Manager, RunEvent, WebviewUrl, WebviewWindowBuilder};
|
||||
|
||||
struct ServerProcess(Mutex<Option<Child>>);
|
||||
|
||||
fn find_python_executable(app: &AppHandle) -> Option<String> {
|
||||
// In development, use system Python
|
||||
if cfg!(debug_assertions) {
|
||||
return Some("python3".to_string());
|
||||
}
|
||||
const SERVER_URL: &str = "http://127.0.0.1:31102";
|
||||
|
||||
// In production, look for bundled Python sidecar
|
||||
let resource_path = app.path().resource_dir().ok()?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let sidecar_path = resource_path.join("sidecar").join("kjvstudy-server");
|
||||
if sidecar_path.exists() {
|
||||
return Some(sidecar_path.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let sidecar_path = resource_path.join("sidecar").join("kjvstudy-server.exe");
|
||||
if sidecar_path.exists() {
|
||||
return Some(sidecar_path.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to system Python
|
||||
Some("python3".to_string())
|
||||
}
|
||||
|
||||
fn start_server(app: &AppHandle) -> Option<Child> {
|
||||
let python = find_python_executable(app)?;
|
||||
|
||||
// Get the resource directory for data files
|
||||
let resource_dir = app.path().resource_dir().ok()?;
|
||||
|
||||
// For development, use the project directory
|
||||
let working_dir = if cfg!(debug_assertions) {
|
||||
std::env::current_dir().ok()?
|
||||
} else {
|
||||
resource_dir.clone()
|
||||
};
|
||||
fn start_server() -> Option<Child> {
|
||||
// Get the current working directory (project root in dev)
|
||||
let working_dir = std::env::current_dir().ok()?;
|
||||
|
||||
println!("Starting KJV Study server...");
|
||||
println!("Python executable: {}", python);
|
||||
println!("Working directory: {:?}", working_dir);
|
||||
|
||||
let child = if python.ends_with("kjvstudy-server") || python.ends_with("kjvstudy-server.exe") {
|
||||
// Running bundled executable
|
||||
Command::new(&python)
|
||||
.current_dir(&working_dir)
|
||||
.env("KJVSTUDY_PORT", "31102")
|
||||
.spawn()
|
||||
.ok()?
|
||||
} else {
|
||||
// Running with Python interpreter (development)
|
||||
Command::new(&python)
|
||||
.args([
|
||||
"-m", "uvicorn",
|
||||
"kjvstudy_org.server:app",
|
||||
"--host", "127.0.0.1",
|
||||
"--port", "31102",
|
||||
"--log-level", "warning"
|
||||
])
|
||||
.current_dir(&working_dir)
|
||||
.spawn()
|
||||
.ok()?
|
||||
};
|
||||
// Try uv first (preferred), then fall back to python3
|
||||
let child = Command::new("uv")
|
||||
.args([
|
||||
"run", "uvicorn",
|
||||
"kjvstudy_org.server:app",
|
||||
"--host", "127.0.0.1",
|
||||
"--port", "31102",
|
||||
"--log-level", "warning"
|
||||
])
|
||||
.current_dir(&working_dir)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.or_else(|_| {
|
||||
println!("uv not found, trying python3...");
|
||||
Command::new("python3")
|
||||
.args([
|
||||
"-m", "uvicorn",
|
||||
"kjvstudy_org.server:app",
|
||||
"--host", "127.0.0.1",
|
||||
"--port", "31102",
|
||||
"--log-level", "warning"
|
||||
])
|
||||
.current_dir(&working_dir)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
println!("Server process started with PID: {}", child.id());
|
||||
Some(child)
|
||||
}
|
||||
|
||||
fn wait_for_server(max_attempts: u32) -> bool {
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(Duration::from_secs(1))
|
||||
.timeout(Duration::from_secs(2))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
for attempt in 1..=max_attempts {
|
||||
println!("Waiting for server... (attempt {}/{})", attempt, max_attempts);
|
||||
|
||||
match client.get("http://127.0.0.1:31102/api/health").send() {
|
||||
match client.get(format!("{}/api/health", SERVER_URL)).send() {
|
||||
Ok(response) if response.status().is_success() => {
|
||||
println!("Server is ready!");
|
||||
return true;
|
||||
}
|
||||
_ => {
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
Ok(response) => {
|
||||
println!("Server responded with status: {}", response.status());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Connection error: {}", e);
|
||||
}
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
|
||||
println!("Server failed to start after {} attempts", max_attempts);
|
||||
@@ -104,30 +80,39 @@ fn wait_for_server(max_attempts: u32) -> bool {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Start server BEFORE Tauri
|
||||
let server_child = start_server();
|
||||
|
||||
if server_child.is_none() {
|
||||
eprintln!("ERROR: Failed to start server process!");
|
||||
eprintln!("Make sure you're running from the project directory with uv or python3 available.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Wait for server to be ready
|
||||
if !wait_for_server(30) {
|
||||
eprintln!("ERROR: Server failed to become ready!");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Now start Tauri
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.manage(ServerProcess(Mutex::new(None)))
|
||||
.manage(ServerProcess(Mutex::new(server_child)))
|
||||
.setup(|app| {
|
||||
// Start the Python server
|
||||
let handle = app.handle().clone();
|
||||
|
||||
if let Some(child) = start_server(&handle) {
|
||||
let state = app.state::<ServerProcess>();
|
||||
*state.0.lock().unwrap() = Some(child);
|
||||
|
||||
// Wait for server to be ready
|
||||
if !wait_for_server(30) {
|
||||
eprintln!("Warning: Server may not be fully ready");
|
||||
}
|
||||
} else {
|
||||
eprintln!("Failed to start server process");
|
||||
}
|
||||
|
||||
// Navigate to the local server
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.eval("window.location.href = 'http://127.0.0.1:31102'");
|
||||
}
|
||||
// Create window pointing to our server
|
||||
let _window = WebviewWindowBuilder::new(
|
||||
app,
|
||||
"main",
|
||||
WebviewUrl::External(SERVER_URL.parse().unwrap())
|
||||
)
|
||||
.title("KJV Study")
|
||||
.inner_size(1200.0, 800.0)
|
||||
.min_inner_size(800.0, 600.0)
|
||||
.center()
|
||||
.build()?;
|
||||
|
||||
println!("Window created, loading {}", SERVER_URL);
|
||||
Ok(())
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
@@ -140,6 +125,7 @@ fn main() {
|
||||
println!("Shutting down server...");
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
println!("Server stopped.");
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user