mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-07-27 07:35:22 +00:00
Demo ready
This commit is contained in:
parent
1c342b60ba
commit
ed618648e0
@ -26,8 +26,10 @@ if errorlevel 1 (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Find the built JAR
|
||||
for %%f in (build\libs\Stirling-PDF-*.jar) do set STIRLING_JAR=%%f
|
||||
REM Find the built JAR(s)
|
||||
echo ▶ Listing all built JAR files in stirling-pdf\build\libs:
|
||||
dir /b stirling-pdf\build\libs\Stirling-PDF-*.jar
|
||||
for %%f in (stirling-pdf\build\libs\Stirling-PDF-*.jar) do set STIRLING_JAR=%%f
|
||||
if not exist "%STIRLING_JAR%" (
|
||||
echo ❌ No Stirling-PDF JAR found in build/libs/
|
||||
exit /b 1
|
||||
@ -43,6 +45,10 @@ echo ▶ Copying JAR to Tauri libs directory...
|
||||
copy "%STIRLING_JAR%" "frontend\src-tauri\libs\"
|
||||
echo ✅ JAR copied to frontend\src-tauri\libs\
|
||||
|
||||
REM Log out all JAR files now in the Tauri libs directory
|
||||
echo ▶ Listing all JAR files in frontend\src-tauri\libs after copy:
|
||||
dir /b frontend\src-tauri\libs\Stirling-PDF-*.jar
|
||||
|
||||
echo ▶ Creating custom JRE with jlink...
|
||||
if exist "frontend\src-tauri\runtime\jre" rmdir /s /q "frontend\src-tauri\runtime\jre"
|
||||
|
||||
|
@ -62,13 +62,13 @@ fi
|
||||
print_step "Building Stirling-PDF JAR..."
|
||||
./gradlew clean bootJar --no-daemon
|
||||
|
||||
if [ ! -f "build/libs/Stirling-PDF-"*.jar ]; then
|
||||
if [ ! -f "stirling-pdf/build/libs/Stirling-PDF-"*.jar ]; then
|
||||
print_error "Failed to build Stirling-PDF JAR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find the built JAR
|
||||
STIRLING_JAR=$(ls build/libs/Stirling-PDF-*.jar | head -n 1)
|
||||
STIRLING_JAR=$(ls stirling-pdf/build/libs/Stirling-PDF-*.jar | head -n 1)
|
||||
print_success "Built JAR: $STIRLING_JAR"
|
||||
|
||||
# Create directories for Tauri
|
||||
|
@ -8,6 +8,7 @@ use std::collections::VecDeque;
|
||||
|
||||
static BACKEND_PROCESS: Mutex<Option<tauri_plugin_shell::process::CommandChild>> = Mutex::new(None);
|
||||
static BACKEND_LOGS: Mutex<VecDeque<String>> = Mutex::new(VecDeque::new());
|
||||
static BACKEND_STARTING: Mutex<bool> = Mutex::new(false);
|
||||
|
||||
// Helper function to add log entry
|
||||
fn add_log(message: String) {
|
||||
@ -20,25 +21,42 @@ fn add_log(message: String) {
|
||||
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
|
||||
// Check if backend is already running or starting
|
||||
{
|
||||
let process_guard = BACKEND_PROCESS.lock().unwrap();
|
||||
if process_guard.is_some() {
|
||||
add_log("⚠️ Backend already running, skipping start".to_string());
|
||||
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
|
||||
})?;
|
||||
|
||||
@ -55,6 +73,7 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -66,26 +85,32 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
|
||||
.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();
|
||||
path.extension().and_then(|s| s.to_str()) == Some("jar")
|
||||
&& path.file_name().unwrap().to_string_lossy().contains("Stirling-PDF")
|
||||
// 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
|
||||
// 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_string();
|
||||
let name_b = b.file_name().to_string_lossy().to_string();
|
||||
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
|
||||
});
|
||||
|
||||
@ -155,6 +180,7 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
|
||||
.map_err(|e| {
|
||||
let error_msg = format!("❌ Failed to spawn sidecar: {}", e);
|
||||
add_log(error_msg.clone());
|
||||
reset_starting_flag();
|
||||
error_msg
|
||||
})?;
|
||||
|
||||
@ -249,6 +275,10 @@ async fn start_backend(app: tauri::AppHandle) -> Result<String, String> {
|
||||
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())
|
||||
}
|
||||
|
||||
@ -319,8 +349,12 @@ async fn check_jar_exists(app: tauri::AppHandle) -> Result<String, String> {
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| {
|
||||
let path = entry.path();
|
||||
path.extension().and_then(|s| s.to_str()) == Some("jar")
|
||||
&& path.file_name().unwrap().to_string_lossy().contains("Stirling-PDF")
|
||||
// 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();
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { RainbowThemeProvider } from './components/shared/RainbowThemeProvider';
|
||||
import React, { useEffect } from 'react';
|
||||
import { RainbowThemeProvider } from './components/shared/RainbowThemeProvider';
|
||||
import HomePage from './pages/HomePage';
|
||||
|
||||
// Import global styles
|
||||
@ -15,11 +14,14 @@ export default function App() {
|
||||
const initializeBackend = async () => {
|
||||
try {
|
||||
// Check if we're running in Tauri environment
|
||||
if (typeof window !== 'undefined' && window.__TAURI__) {
|
||||
if (typeof window !== 'undefined' && (window.__TAURI__ || window.__TAURI_INTERNALS__)) {
|
||||
const { tauriBackendService } = await import('./services/tauriBackendService');
|
||||
console.log('Running in Tauri - Starting backend on React app startup...');
|
||||
await tauriBackendService.startBackend();
|
||||
console.log('Backend started successfully');
|
||||
}
|
||||
else {
|
||||
console.warn('Not running in Tauri - Backend will not be started');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to start backend on app startup:', error);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { makeApiUrl } from '../utils/api';
|
||||
|
||||
export interface AppConfig {
|
||||
baseUrl?: string;
|
||||
@ -46,7 +47,7 @@ export function useAppConfig(): UseAppConfigReturn {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const response = await fetch('/api/v1/config/app-config');
|
||||
const response = await fetch(makeApiUrl('/api/v1/config/app-config'));
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch config: ${response.status} ${response.statusText}`);
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { makeApiUrl } from '../utils/api';
|
||||
import { useBackendHealth } from './useBackendHealth';
|
||||
|
||||
/**
|
||||
* Hook to check if a specific endpoint is enabled
|
||||
*/
|
||||
export function useEndpointEnabled(endpoint: string): {
|
||||
export function useEndpointEnabled(endpoint: string, backendHealthy?: boolean): {
|
||||
enabled: boolean | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
@ -24,7 +26,7 @@ export function useEndpointEnabled(endpoint: string): {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const response = await fetch(`/api/v1/config/endpoint-enabled?endpoint=${encodeURIComponent(endpoint)}`);
|
||||
const response = await fetch(makeApiUrl(`/api/v1/config/endpoint-enabled?endpoint=${encodeURIComponent(endpoint)}`));
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to check endpoint: ${response.status} ${response.statusText}`);
|
||||
@ -42,8 +44,16 @@ export function useEndpointEnabled(endpoint: string): {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchEndpointStatus();
|
||||
}, [endpoint]);
|
||||
// Only fetch endpoint status if backend is healthy (or if backendHealthy is not provided)
|
||||
if (backendHealthy === undefined || backendHealthy === true) {
|
||||
fetchEndpointStatus();
|
||||
} else {
|
||||
// Backend is not healthy, reset state
|
||||
setEnabled(null);
|
||||
setLoading(false);
|
||||
setError('Backend not available');
|
||||
}
|
||||
}, [endpoint, backendHealthy]);
|
||||
|
||||
return {
|
||||
enabled,
|
||||
@ -57,7 +67,7 @@ export function useEndpointEnabled(endpoint: string): {
|
||||
* Hook to check multiple endpoints at once using batch API
|
||||
* Returns a map of endpoint -> enabled status
|
||||
*/
|
||||
export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
export function useMultipleEndpointsEnabled(endpoints: string[], backendHealthy?: boolean): {
|
||||
endpointStatus: Record<string, boolean>;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
@ -80,7 +90,7 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
|
||||
// Use batch API for efficiency
|
||||
const endpointsParam = endpoints.join(',');
|
||||
const response = await fetch(`/api/v1/config/endpoints-enabled?endpoints=${encodeURIComponent(endpointsParam)}`);
|
||||
const response = await fetch(makeApiUrl(`/api/v1/config/endpoints-enabled?endpoints=${encodeURIComponent(endpointsParam)}`));
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to check endpoints: ${response.status} ${response.statusText}`);
|
||||
@ -105,8 +115,16 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAllEndpointStatuses();
|
||||
}, [endpoints.join(',')]); // Re-run when endpoints array changes
|
||||
// Only fetch endpoint statuses if backend is healthy (or if backendHealthy is not provided)
|
||||
if (backendHealthy === undefined || backendHealthy === true) {
|
||||
fetchAllEndpointStatuses();
|
||||
} else {
|
||||
// Backend is not healthy, reset state
|
||||
setEndpointStatus({});
|
||||
setLoading(false);
|
||||
setError('Backend not available');
|
||||
}
|
||||
}, [endpoints.join(','), backendHealthy]); // Re-run when endpoints array changes or backend health changes
|
||||
|
||||
return {
|
||||
endpointStatus,
|
||||
@ -114,4 +132,50 @@ export function useMultipleEndpointsEnabled(endpoints: string[]): {
|
||||
error,
|
||||
refetch: fetchAllEndpointStatuses,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience hook that combines backend health checking with endpoint status checking
|
||||
* Only checks endpoint status once backend is healthy
|
||||
*/
|
||||
export function useEndpointEnabledWithHealthCheck(endpoint: string): {
|
||||
enabled: boolean | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
backendHealthy: boolean;
|
||||
refetch: () => Promise<void>;
|
||||
} {
|
||||
const { isHealthy: backendHealthy } = useBackendHealth();
|
||||
const { enabled, loading, error, refetch } = useEndpointEnabled(endpoint, backendHealthy);
|
||||
|
||||
return {
|
||||
enabled,
|
||||
loading,
|
||||
error,
|
||||
backendHealthy,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience hook that combines backend health checking with multiple endpoint status checking
|
||||
* Only checks endpoint statuses once backend is healthy
|
||||
*/
|
||||
export function useMultipleEndpointsEnabledWithHealthCheck(endpoints: string[]): {
|
||||
endpointStatus: Record<string, boolean>;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
backendHealthy: boolean;
|
||||
refetch: () => Promise<void>;
|
||||
} {
|
||||
const { isHealthy: backendHealthy } = useBackendHealth();
|
||||
const { endpointStatus, loading, error, refetch } = useMultipleEndpointsEnabled(endpoints, backendHealthy);
|
||||
|
||||
return {
|
||||
endpointStatus,
|
||||
loading,
|
||||
error,
|
||||
backendHealthy,
|
||||
refetch,
|
||||
};
|
||||
}
|
@ -24,7 +24,7 @@ import CompressPdfPanel from "../tools/Compress";
|
||||
import MergePdfPanel from "../tools/Merge";
|
||||
import ToolRenderer from "../components/tools/ToolRenderer";
|
||||
import QuickAccessBar from "../components/shared/QuickAccessBar";
|
||||
import { useMultipleEndpointsEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useMultipleEndpointsEnabledWithHealthCheck } from "../hooks/useEndpointConfig";
|
||||
|
||||
type ToolRegistryEntry = {
|
||||
icon: React.ReactNode;
|
||||
@ -79,7 +79,7 @@ export default function HomePage() {
|
||||
|
||||
// Get all unique endpoints for batch checking
|
||||
const allEndpoints = Array.from(new Set(Object.values(toolEndpoints).flat()));
|
||||
const { endpointStatus, loading: endpointsLoading } = useMultipleEndpointsEnabled(allEndpoints);
|
||||
const { endpointStatus, loading: endpointsLoading, backendHealthy } = useMultipleEndpointsEnabledWithHealthCheck(allEndpoints);
|
||||
|
||||
// Persist active files across reloads
|
||||
useEffect(() => {
|
||||
|
@ -4,7 +4,7 @@ import { Stack, Slider, Group, Text, Button, Checkbox, TextInput, Loader, Alert
|
||||
import { FileWithUrl } from "../types/file";
|
||||
import { fileStorage } from "../services/fileStorage";
|
||||
import { makeApiUrl } from "../utils/api";
|
||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useEndpointEnabledWithHealthCheck } from "../hooks/useEndpointConfig";
|
||||
|
||||
export interface CompressProps {
|
||||
files?: FileWithUrl[];
|
||||
@ -37,7 +37,7 @@ const CompressPdfPanel: React.FC<CompressProps> = ({
|
||||
|
||||
const [selected, setSelected] = useState<boolean[]>(files.map(() => false));
|
||||
const [localLoading, setLocalLoading] = useState<boolean>(false);
|
||||
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("compress-pdf");
|
||||
const { enabled: endpointEnabled, loading: endpointLoading, backendHealthy } = useEndpointEnabledWithHealthCheck("compress-pdf");
|
||||
|
||||
const {
|
||||
compressionLevel,
|
||||
|
@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { FileWithUrl } from "../types/file";
|
||||
import { fileStorage } from "../services/fileStorage";
|
||||
import { makeApiUrl } from "../utils/api";
|
||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useEndpointEnabledWithHealthCheck } from "../hooks/useEndpointConfig";
|
||||
|
||||
export interface MergePdfPanelProps {
|
||||
files: FileWithUrl[];
|
||||
@ -28,7 +28,7 @@ const MergePdfPanel: React.FC<MergePdfPanelProps> = ({
|
||||
const [downloadUrl, setLocalDownloadUrl] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("merge-pdfs");
|
||||
const { enabled: endpointEnabled, loading: endpointLoading, backendHealthy } = useEndpointEnabledWithHealthCheck("merge-pdfs");
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedFiles(files.map(() => true));
|
||||
|
@ -17,7 +17,7 @@ import { useTranslation } from "react-i18next";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import { FileWithUrl } from "../types/file";
|
||||
import { fileStorage } from "../services/fileStorage";
|
||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useEndpointEnabledWithHealthCheck } from "../hooks/useEndpointConfig";
|
||||
|
||||
export interface SplitPdfPanelProps {
|
||||
file: { file: FileWithUrl; url: string } | null;
|
||||
@ -83,7 +83,7 @@ const SplitPdfPanel: React.FC<SplitPdfPanelProps> = ({
|
||||
} = params;
|
||||
|
||||
|
||||
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(getEndpointName(mode));
|
||||
const { enabled: endpointEnabled, loading: endpointLoading, backendHealthy } = useEndpointEnabledWithHealthCheck(getEndpointName(mode));
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!file) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user