From aed4ccd07ba376e493c4aa23fad6c5a26410e7d6 Mon Sep 17 00:00:00 2001 From: Connor Yoh Date: Mon, 9 Jun 2025 10:04:40 +0100 Subject: [PATCH] Building tauri on windows --- build-tauri.bat | 28 ++ build-tauri.sh | 31 ++ frontend/README.md | 12 + frontend/package-lock.json | 11 + frontend/package.json | 1 + frontend/src-tauri/Cargo.lock | 564 ++++++++++++++++++++-- frontend/src-tauri/Cargo.toml | 3 + frontend/src-tauri/build.rs | 2 +- frontend/src-tauri/src/lib.rs | 436 +++++++++++++++++ frontend/src-tauri/tauri.conf.json | 14 +- frontend/src/App.tsx | 24 +- frontend/src/components/BackendStatus.tsx | 124 +++++ frontend/src/components/SidecarTest.tsx | 315 ++++++++++++ frontend/src/services/backendService.ts | 73 +++ 14 files changed, 1604 insertions(+), 34 deletions(-) create mode 100644 build-tauri.bat create mode 100644 build-tauri.sh create mode 100644 frontend/src/components/BackendStatus.tsx create mode 100644 frontend/src/components/SidecarTest.tsx create mode 100644 frontend/src/services/backendService.ts diff --git a/build-tauri.bat b/build-tauri.bat new file mode 100644 index 000000000..ef04a48fd --- /dev/null +++ b/build-tauri.bat @@ -0,0 +1,28 @@ +@echo off +echo 🔨 Building Stirling PDF with Tauri integration... + +REM Build the Java backend +echo đŸ“Ļ Building Java backend... +call gradlew.bat bootJar + +if %ERRORLEVEL% neq 0 ( + echo ❌ Failed to build Java backend + exit /b 1 +) + +echo ✅ Java backend built successfully + +REM Copy the JAR to Tauri resources +echo 📋 Copying JAR file to Tauri resources... +if not exist "frontend\src-tauri\libs" mkdir frontend\src-tauri\libs +copy "build\libs\Stirling-PDF-*.jar" "frontend\src-tauri\libs\" +if %ERRORLEVEL% neq 0 ( + echo ❌ Failed to copy JAR file + exit /b 1 +) +echo ✅ JAR copied successfully + +REM Navigate to frontend and run Tauri +echo 🚀 Starting Tauri development server... +cd frontend +npx tauri dev \ No newline at end of file diff --git a/build-tauri.sh b/build-tauri.sh new file mode 100644 index 000000000..b4fb9e3b6 --- /dev/null +++ b/build-tauri.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +echo "🔨 Building Stirling PDF with Tauri integration..." + +# Build the Java backend +echo "đŸ“Ļ Building Java backend..." +./gradlew bootJar + +if [ $? -ne 0 ]; then + echo "❌ Failed to build Java backend" + exit 1 +fi + +echo "✅ Java backend built successfully" + +# Copy the JAR to Tauri resources +echo "📋 Copying JAR file to Tauri resources..." +mkdir -p frontend/src-tauri/libs +cp build/libs/Stirling-PDF-*.jar frontend/src-tauri/libs/ +if [ $? -eq 0 ]; then + echo "✅ JAR copied successfully" +else + echo "❌ Failed to copy JAR file" + exit 1 +fi + + +# Navigate to frontend and run Tauri +echo "🚀 Starting Tauri development server..." +cd frontend +npm run tauri dev \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index 58beeaccd..52be84b38 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -68,3 +68,15 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/d ### `npm run build` fails to minify This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) + + +## Tauri +### Dev +To run Tauri in development. Use the command: +````npm run tauri-dev``` +This will run the gradle runboot command and the tauri dev command concurrently, starting the app once both are stable. + +### Build +To build a deployment of the Tauri app. Use the command: +```npm run tauri-build``` +This will bundle the backend and frontend into one executable for each target. Targets can be set within the `tauri.conf.json` file. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4cfd2f208..c8d278100 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "@mui/icons-material": "^7.1.0", "@mui/material": "^7.1.0", "@tailwindcss/postcss": "^4.1.8", + "@tauri-apps/api": "^2.5.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", @@ -2001,6 +2002,16 @@ "tailwindcss": "4.1.8" } }, + "node_modules/@tauri-apps/api": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.5.0.tgz", + "integrity": "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, "node_modules/@tauri-apps/cli": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.5.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index a2b583b8d..e2ebc2087 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "@mui/icons-material": "^7.1.0", "@mui/material": "^7.1.0", "@tailwindcss/postcss": "^4.1.8", + "@tauri-apps/api": "^2.5.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index 61642386a..d0fdec58e 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -95,11 +95,14 @@ name = "app" version = "0.1.0" dependencies = [ "log", + "reqwest 0.11.27", "serde", "serde_json", "tauri", "tauri-build", "tauri-plugin-log", + "tauri-plugin-shell", + "tokio", ] [[package]] @@ -476,6 +479,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -499,9 +512,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.9.1", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -512,7 +525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.9.1", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -782,7 +795,7 @@ dependencies = [ "rustc_version", "toml", "vswhom", - "winreg", + "winreg 0.55.0", ] [[package]] @@ -791,6 +804,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -817,6 +839,22 @@ dependencies = [ "typeid", ] +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fdeflate" version = "0.3.7" @@ -861,6 +899,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -868,7 +915,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -882,6 +929,12 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1291,6 +1344,25 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.9.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1338,6 +1410,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.15", +] + [[package]] name = "http" version = "1.3.1" @@ -1349,6 +1432,17 @@ dependencies = [ "itoa 1.0.15", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1356,7 +1450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.3.1", ] [[package]] @@ -1367,8 +1461,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "pin-project-lite", ] @@ -1378,6 +1472,36 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa 1.0.15", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.6.0" @@ -1387,8 +1511,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "httparse", "itoa 1.0.15", "pin-project-lite", @@ -1397,6 +1521,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.32", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-util" version = "0.1.14" @@ -1408,9 +1545,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", - "http-body", - "hyper", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", "ipnet", "libc", "percent-encoding", @@ -1615,6 +1752,25 @@ dependencies = [ "serde", ] +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "itoa" version = "0.4.8" @@ -1784,6 +1940,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "litemap" version = "0.8.0" @@ -1898,6 +2060,23 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2214,12 +2393,78 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "os_pipe" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "pango" version = "0.18.3" @@ -2268,6 +2513,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2744,6 +2995,46 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + [[package]] name = "reqwest" version = "0.12.19" @@ -2754,10 +3045,10 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.6.0", "hyper-util", "ipnet", "js-sys", @@ -2769,7 +3060,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-util", "tower", @@ -2842,6 +3133,28 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustversion" version = "1.0.21" @@ -2863,6 +3176,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "schemars" version = "0.8.22" @@ -2902,6 +3224,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.22.0" @@ -3090,6 +3435,16 @@ dependencies = [ "digest", ] +[[package]] +name = "shared_child" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e297bd52991bbe0686c086957bee142f13df85d1e79b0b21630a99d374ae9dc" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3154,7 +3509,7 @@ dependencies = [ "bytemuck", "cfg_aliases", "core-graphics", - "foreign-types", + "foreign-types 0.5.0", "js-sys", "log", "objc2 0.5.2", @@ -3263,6 +3618,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3283,6 +3644,27 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3303,7 +3685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" dependencies = [ "bitflags 2.9.1", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dispatch", @@ -3374,7 +3756,7 @@ dependencies = [ "glob", "gtk", "heck 0.5.0", - "http", + "http 1.3.1", "jni", "libc", "log", @@ -3387,7 +3769,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.12.19", "serde", "serde_json", "serde_repr", @@ -3511,6 +3893,27 @@ dependencies = [ "time", ] +[[package]] +name = "tauri-plugin-shell" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d5eb3368b959937ad2aeaf6ef9a8f5d11e01ffe03629d3530707bbcb27ff5d" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "tokio", +] + [[package]] name = "tauri-runtime" version = "2.6.0" @@ -3520,7 +3923,7 @@ dependencies = [ "cookie", "dpi", "gtk", - "http", + "http 1.3.1", "jni", "objc2 0.6.1", "objc2-ui-kit", @@ -3540,7 +3943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f85d056f4d4b014fe874814034f3416d57114b617a493a4fe552580851a3f3a2" dependencies = [ "gtk", - "http", + "http 1.3.1", "jni", "log", "objc2 0.6.1", @@ -3573,7 +3976,7 @@ dependencies = [ "dunce", "glob", "html5ever", - "http", + "http 1.3.1", "infer", "json-patch", "kuchikiki", @@ -3609,6 +4012,19 @@ dependencies = [ "toml", ] +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "tendril" version = "0.4.3" @@ -3739,6 +4155,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.15" @@ -3824,7 +4250,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -3839,8 +4265,8 @@ dependencies = [ "bitflags 2.9.1", "bytes", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "iri-string", "pin-project-lite", "tower", @@ -4032,6 +4458,12 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.0" @@ -4435,6 +4867,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -4468,6 +4909,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4508,6 +4964,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4520,6 +4982,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4532,6 +5000,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4550,6 +5024,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4562,6 +5042,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4574,6 +5060,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4586,6 +5078,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4610,6 +5108,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.55.0" @@ -4650,7 +5158,7 @@ dependencies = [ "gdkx11", "gtk", "html5ever", - "http", + "http 1.3.1", "javascriptcore-rs", "jni", "kuchikiki", diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index 099ccca25..f04a9c2be 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -23,3 +23,6 @@ serde = { version = "1.0", features = ["derive"] } log = "0.4" tauri = { version = "2.5.0", features = [] } tauri-plugin-log = "2.0.0-rc" +tauri-plugin-shell = "2.1.0" +tokio = { version = "1.0", features = ["time"] } +reqwest = { version = "0.11", features = ["json"] } diff --git a/frontend/src-tauri/build.rs b/frontend/src-tauri/build.rs index 795b9b7c8..d860e1e6a 100644 --- a/frontend/src-tauri/build.rs +++ b/frontend/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 9c3118c5d..55ecb419d 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -1,6 +1,426 @@ +use tauri_plugin_shell::ShellExt; +use tauri::Manager; + +// Store backend process handle and logs globally +use std::sync::Mutex; +use std::sync::Arc; +use std::collections::VecDeque; + +static BACKEND_PROCESS: Mutex>> = Mutex::new(None); +static BACKEND_LOGS: Mutex> = Mutex::new(VecDeque::new()); + +// 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 +} + +// Command to get backend logs +#[tauri::command] +async fn get_backend_logs() -> Result, String> { + let logs = BACKEND_LOGS.lock().unwrap(); + Ok(logs.iter().cloned().collect()) +} + +// Command to start the backend sidecar +#[tauri::command] +async fn start_backend(app: tauri::AppHandle) -> Result { + add_log("🚀 Attempting to start backend sidecar...".to_string()); + + // Check if backend is already running + { + let process_guard = BACKEND_PROCESS.lock().unwrap(); + if process_guard.is_some() { + add_log("âš ī¸ Backend already running, skipping start".to_string()); + return Ok("Backend already running".to_string()); + } + } + + add_log("📋 Creating Java command to run JAR directly".to_string()); + + // Use Tauri's resource API to find the JAR file + 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()); + error_msg + })?; + + add_log(format!("🔍 Looking for JAR in Tauri resource directory: {:?}", resource_dir)); + + // In dev mode, resources are in target/debug/libs, in production they're bundled + let libs_dir = resource_dir.join("libs"); + add_log(format!("🔍 Checking libs directory: {:?}", libs_dir)); + + // Find all Stirling-PDF JAR files and pick the latest version + 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 frontend/src-tauri/libs/", e); + add_log(error_msg.clone()); + 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") + }) + .collect(); + + if jar_files.is_empty() { + let error_msg = "No Stirling-PDF JAR found in Tauri resources/libs directory. Please run the build script to generate and copy the JAR.".to_string(); + add_log(error_msg.clone()); + return Err(error_msg); + } + + // Sort by filename to get the latest version (assumes semantic versioning in filename) + 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(); + name_b.cmp(&name_a) // Reverse order to get latest first + }); + + let jar_path = jar_files[0].path(); + add_log(format!("📋 Selected latest JAR from {} available: {:?}", jar_files.len(), jar_path.file_name().unwrap())); + + // Normalize the path to remove Windows UNC prefix \\?\ + 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 in resources: {:?}", jar_path)); + add_log(format!("đŸ“Ļ Normalized JAR path: {:?}", normalized_jar_path)); + + // Log the equivalent command for external testing + let java_command = format!( + "java -Xmx2g -DBROWSER_OPEN=false -DSTIRLING_PDF_DESKTOP_UI=true -jar \"{}\"", + normalized_jar_path.display() + ); + add_log(format!("🔧 Equivalent command to run externally: {}", java_command)); + + // Create Java command directly + let sidecar_command = app + .shell() + .command("java") + .args([ + "-Xmx2g", + "-DBROWSER_OPEN=false", + "-jar", + normalized_jar_path.to_str().unwrap() + ]); + + add_log("âš™ī¸ Sidecar command created, attempting to spawn...".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()); + error_msg + })?; + + // Store the process handle + { + let mut process_guard = BACKEND_PROCESS.lock().unwrap(); + *process_guard = Some(Arc::new(child)); + } + + add_log("✅ Sidecar spawned successfully, 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("Tomcat started") || + output_str.contains("Started on port") || + output_str.contains("Netty started") || + output_str.contains("Started StirlingPDF") { + 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(5000)).await; + + Ok("Backend startup initiated successfully".to_string()) +} + +// Command to check if backend is healthy +#[tauri::command] +async fn check_backend_health() -> Result { + println!("🔍 Checking backend health..."); + 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/actuator/health").send().await { + Ok(response) => { + let status = response.status(); + println!("💓 Health check response status: {}", status); + if status.is_success() { + match response.text().await { + Ok(body) => { + println!("💓 Health check response: {}", 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 backend process status +#[tauri::command] +async fn get_backend_status() -> Result { + let process_guard = BACKEND_PROCESS.lock().unwrap(); + match process_guard.as_ref() { + Some(child) => { + // Try to check if process is still alive + let pid = child.pid(); + println!("🔍 Checking backend process status, PID: {}", pid); + Ok(format!("Backend process is running (PID: {})", pid)) + }, + None => Ok("Backend process is not running".to_string()), + } +} + +// Command to check if backend port is accessible +#[tauri::command] +async fn check_backend_port() -> Result { + println!("🔍 Checking if port 8080 is accessible..."); + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(3)) + .build() + .map_err(|e| format!("Failed to create HTTP client: {}", e))?; + + match client.head("http://localhost:8080/").send().await { + Ok(response) => { + println!("✅ Port 8080 responded with status: {}", response.status()); + Ok(true) + } + Err(e) => { + println!("❌ Port 8080 not accessible: {}", e); + Ok(false) + } + } +} + +// Command to check if JAR file exists +#[tauri::command] +async fn check_jar_exists(app: tauri::AppHandle) -> Result { + println!("🔍 Checking for JAR files in Tauri resources..."); + + // Check in the Tauri resource directory (bundled) + if let Ok(resource_dir) = app.path().resource_dir() { + let jar_path = resource_dir; + println!("Checking bundled resources: {:?}", jar_path); + + if jar_path.exists() { + match std::fs::read_dir(&jar_path) { + Ok(entries) => { + let mut jar_files = Vec::new(); + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if path.extension().and_then(|s| s.to_str()) == Some("jar") + && path.file_name() + .unwrap() + .to_string_lossy() + .contains("Stirling-PDF") { + jar_files.push(path.file_name().unwrap().to_string_lossy().to_string()); + } + } + } + if !jar_files.is_empty() { + println!("✅ Found JAR files in bundled resources: {:?}", jar_files); + return Ok(format!("Found JAR files: {:?}", jar_files)); + } + } + Err(e) => { + println!("❌ Failed to read resource directory: {}", e); + } + } + } + } + + // Check in development mode location (libs directory) + let dev_jar_path = std::path::PathBuf::from("libs"); + println!("Checking development libs directory: {:?}", dev_jar_path); + + if dev_jar_path.exists() { + match std::fs::read_dir(&dev_jar_path) { + Ok(entries) => { + let mut jar_files = Vec::new(); + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if path.extension().and_then(|s| s.to_str()) == Some("jar") + && path.file_name() + .unwrap() + .to_string_lossy() + .contains("Stirling-PDF") { + jar_files.push(path.file_name().unwrap().to_string_lossy().to_string()); + } + } + } + if !jar_files.is_empty() { + println!("✅ Found JAR files in development libs: {:?}", jar_files); + return Ok(format!("Found JAR files: {:?}", jar_files)); + } + } + Err(e) => { + println!("❌ Failed to read libs directory: {}", e); + } + } + } + + println!("❌ No Stirling-PDF JAR files found"); + Ok("No Stirling-PDF JAR files found. Please run './build-tauri.sh' or 'build-tauri.bat' to build and copy the JAR.".to_string()) +} + +// Command to test sidecar binary directly +#[tauri::command] +async fn test_sidecar_binary(app: tauri::AppHandle) -> Result { + println!("🔍 Testing sidecar binary availability..."); + + // Test if we can create the sidecar command (this validates the binary exists) + match app.shell().sidecar("stirling-pdf-backend") { + Ok(_) => { + println!("✅ Sidecar binary 'stirling-pdf-backend' is available"); + Ok("Sidecar binary 'stirling-pdf-backend' is available and can be executed".to_string()) + } + Err(e) => { + println!("❌ Failed to access sidecar binary: {}", e); + Ok(format!("Sidecar binary not available: {}. Make sure the binary exists in the binaries/ directory with correct permissions.", e)) + } + } +} + +// Command to check Java environment +#[tauri::command] +async fn check_java_environment() -> Result { + println!("🔍 Checking Java environment..."); + + let output = std::process::Command::new("java") + .arg("--version") + .output(); + + match output { + Ok(output) => { + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let version_info = if !stdout.is_empty() { stdout } else { stderr }; + println!("✅ Java found: {}", version_info); + Ok(format!("Java available: {}", version_info.trim())) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + println!("❌ Java command failed: {}", stderr); + Ok(format!("Java command failed: {}", stderr)) + } + } + Err(e) => { + println!("❌ Java not found: {}", e); + Ok(format!("Java not found or not in PATH: {}", e)) + } + } +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) .setup(|app| { if cfg!(debug_assertions) { app.handle().plugin( @@ -9,8 +429,24 @@ pub fn run() { .build(), )?; } + + // Automatically start the backend when Tauri starts + let app_handle = app.handle().clone(); + tauri::async_runtime::spawn(async move { + tokio::time::sleep(std::time::Duration::from_millis(1000)).await; // Small delay to ensure app is ready + match start_backend(app_handle).await { + Ok(result) => { + add_log(format!("🚀 Auto-started backend on Tauri startup: {}", result)); + } + Err(error) => { + add_log(format!("❌ Failed to auto-start backend: {}", error)); + } + } + }); + Ok(()) }) + .invoke_handler(tauri::generate_handler![start_backend, check_backend_health, check_jar_exists, test_sidecar_binary, get_backend_status, check_backend_port, check_java_environment, get_backend_logs]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index e67bf2b5f..3b83c98cd 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -1,10 +1,10 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "Stirling-PDF", - "version": "0.1.0", - "identifier": "com.tauri.dev", + "version": "2.0.0", + "identifier": "stirling.pdf.dev", "build": { - "frontendDist": "./public", + "frontendDist": "../dist", "devUrl": "http://localhost:5173", "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build" @@ -30,6 +30,14 @@ "icons/32x32.png", "icons/icon.icns", "icons/icon.ico" + ], + "resources": [ + "libs/*.jar" ] + }, + "plugins": { + "shell": { + "open": true + } } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e75a4ff3e..db1cfc796 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,26 @@ import './index.css'; -import React from 'react'; +import React, { useState } from 'react'; import HomePage from './pages/HomePage'; +import { SidecarTest } from './components/SidecarTest'; + export default function App() { - return ; + const [showTests, setShowTests] = useState(true); // Start with tests visible + + return ( +
+
+
+

Stirling PDF - Tauri Integration

+ +
+
+ + {showTests ? : } +
+ ); } diff --git a/frontend/src/components/BackendStatus.tsx b/frontend/src/components/BackendStatus.tsx new file mode 100644 index 000000000..dd821de89 --- /dev/null +++ b/frontend/src/components/BackendStatus.tsx @@ -0,0 +1,124 @@ +import React, { useState, useEffect } from 'react'; +import { invoke } from '@tauri-apps/api/core'; +import { backendService } from '../services/backendService'; + +interface BackendStatusProps {} + +export const BackendStatus: React.FC = () => { + const [status, setStatus] = useState<'starting' | 'healthy' | 'error' | 'unknown'>('unknown'); + const [error, setError] = useState(null); + const [debugInfo, setDebugInfo] = useState([]); + + useEffect(() => { + runDiagnostics(); + }, []); + + const runDiagnostics = async () => { + const info: string[] = []; + + try { + // Check if JAR exists + const jarCheck = await invoke('check_jar_exists') as string; + info.push(`JAR Check: ${jarCheck}`); + + // Check if sidecar binary exists + const binaryCheck = await invoke('test_sidecar_binary') as string; + info.push(`Binary Check: ${binaryCheck}`); + + setDebugInfo(info); + } catch (err) { + info.push(`Diagnostic Error: ${err}`); + setDebugInfo(info); + } + }; + + const initializeBackend = async () => { + try { + setStatus('starting'); + setError(null); + + await backendService.startBackend(); + setStatus('healthy'); + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error'); + setStatus('error'); + } + }; + + const checkHealth = async () => { + const isHealthy = await backendService.checkHealth(); + setStatus(isHealthy ? 'healthy' : 'error'); + }; + + const getStatusColor = () => { + switch (status) { + case 'healthy': return 'text-green-600'; + case 'starting': return 'text-yellow-600'; + case 'error': return 'text-red-600'; + default: return 'text-gray-600'; + } + }; + + const getStatusText = () => { + switch (status) { + case 'healthy': return 'Backend Running'; + case 'starting': return 'Starting Backend...'; + case 'error': return 'Backend Error'; + default: return 'Backend Status Unknown'; + } + }; + + return ( +
+
+

Backend Status

+ +
+ +
+ {getStatusText()} +
+ + {error && ( +
+ Error: {error} +
+ )} + + {status === 'healthy' && ( +
+ Backend URL: {backendService.getBackendUrl()} +
+ )} + + {status === 'error' && ( + + )} + +
+

Debug Information:

+
+ {debugInfo.map((info, index) => ( +
{info}
+ ))} +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/frontend/src/components/SidecarTest.tsx b/frontend/src/components/SidecarTest.tsx new file mode 100644 index 000000000..54d9dbe75 --- /dev/null +++ b/frontend/src/components/SidecarTest.tsx @@ -0,0 +1,315 @@ +import React, { useState, useEffect } from 'react'; +import { invoke } from '@tauri-apps/api/core'; + +interface DiagnosticResult { + timestamp: string; + level: 'info' | 'warning' | 'error' | 'success'; + message: string; + details?: string; +} + +export const SidecarTest: React.FC = () => { + const [logs, setLogs] = useState([]); + const [isRunning, setIsRunning] = useState(false); + const [autoStart, setAutoStart] = useState(true); + + const addLog = (level: DiagnosticResult['level'], message: string, details?: string) => { + const timestamp = new Date().toLocaleTimeString(); + setLogs(prev => [...prev, { timestamp, level, message, details }]); + console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`, details || ''); + }; + + const clearLogs = () => setLogs([]); + + const runDiagnostics = async () => { + setIsRunning(true); + clearLogs(); + + addLog('info', 'Starting comprehensive sidecar diagnostics...'); + + try { + // Step 1: Environment Check + addLog('info', 'Checking environment...'); + + // Check Java first + try { + const javaResult = await invoke('check_java_environment') as string; + addLog('info', 'Java Environment Check:', javaResult); + if (javaResult.includes('not found') || javaResult.includes('failed')) { + addLog('error', 'Java is not available', 'Please install Java 17+ and ensure it is in your PATH'); + setIsRunning(false); + return; + } + } catch (error) { + addLog('error', 'Failed to check Java environment', String(error)); + setIsRunning(false); + return; + } + + try { + const jarResult = await invoke('check_jar_exists') as string; + addLog('info', 'JAR Check Result:', jarResult); + if (!jarResult.includes('Found JAR files')) { + addLog('error', 'No JAR files found - build required!', 'You need to build the Java backend first'); + addLog('info', 'To fix this, run one of these commands:'); + addLog('info', 'â€ĸ Linux/Mac: ./build-tauri.sh'); + addLog('info', 'â€ĸ Windows: build-tauri.bat'); + addLog('info', 'â€ĸ Or manually: ./gradlew bootJar && cd frontend && npx tauri dev'); + addLog('info', 'The JAR should be created in build/libs/ directory'); + setIsRunning(false); + return; + } + } catch (error) { + addLog('error', 'Failed to check JAR files', String(error)); + setIsRunning(false); + return; + } + + try { + const binaryResult = await invoke('test_sidecar_binary') as string; + addLog('info', 'Binary Check Result:', binaryResult); + if (!binaryResult.includes('Binary exists')) { + addLog('error', 'Sidecar binary not found', binaryResult); + setIsRunning(false); + return; + } + } catch (error) { + addLog('error', 'Failed to check sidecar binary', String(error)); + setIsRunning(false); + return; + } + + // Step 2: Start Backend + addLog('info', 'Attempting to start backend sidecar...'); + try { + const startResult = await invoke('start_backend') as string; + addLog('info', 'Backend start command result:', startResult); + + // Wait a moment for process to initialize + await new Promise(resolve => setTimeout(resolve, 3000)); + + // Check if process is actually running + const statusResult = await invoke('get_backend_status') as string; + addLog('info', 'Backend process status:', statusResult); + + if (statusResult.includes('not running')) { + addLog('error', 'Backend process failed to start or crashed immediately'); + addLog('info', 'This could be due to:'); + addLog('info', '- Java not installed or not in PATH'); + addLog('info', '- Port 8080 already in use'); + addLog('info', '- JAR file corruption'); + addLog('info', '- Missing dependencies'); + } + + } catch (error) { + addLog('error', 'Failed to start backend', String(error)); + } + + // Step 3: Port Testing + addLog('info', 'Testing port connectivity...'); + let attempts = 0; + const maxAttempts = 15; + let connected = false; + + while (attempts < maxAttempts && !connected) { + attempts++; + addLog('info', `Port test attempt ${attempts}/${maxAttempts}...`); + + try { + const portResult = await invoke('check_backend_port') as boolean; + if (portResult) { + addLog('success', 'Port 8080 is responding!'); + connected = true; + break; + } + } catch (error) { + addLog('warning', `Port check via Rust failed: ${error}`); + } + + // Fallback: direct fetch + try { + const response = await fetch('http://localhost:8080/', { + method: 'HEAD', + signal: AbortSignal.timeout(3000) + }); + addLog('success', `Direct HTTP test successful: ${response.status}`); + connected = true; + break; + } catch (fetchError) { + addLog('warning', `HTTP test failed: ${fetchError}`); + } + + if (attempts < maxAttempts) { + await new Promise(resolve => setTimeout(resolve, 4000)); + } + } + + if (!connected) { + addLog('error', 'Backend is not responding on port 8080 after 60 seconds'); + addLog('info', 'Possible solutions:'); + addLog('info', '1. Check if Java is installed: java --version'); + addLog('info', '2. Check if port 8080 is free: netstat -an | grep 8080'); + addLog('info', '3. Try running the JAR manually from terminal'); + addLog('info', '4. Check firewall settings'); + } else { + // Step 4: Get detailed sidecar logs + addLog('info', 'Fetching detailed sidecar logs...'); + try { + const sidecarLogs = await invoke('get_backend_logs') as string[]; + if (sidecarLogs.length > 0) { + addLog('info', 'Sidecar execution logs:'); + sidecarLogs.forEach(log => { + addLog('info', log); + }); + } else { + addLog('warning', 'No sidecar logs available - this suggests the sidecar never started'); + } + } catch (error) { + addLog('error', 'Failed to get sidecar logs', String(error)); + } + + // Step 5: API Testing + addLog('info', 'Testing API endpoints...'); + + const endpoints = [ + { path: '/', description: 'Home page' }, + { path: '/actuator/health', description: 'Health endpoint' }, + { path: '/actuator/info', description: 'Info endpoint' } + ]; + + for (const endpoint of endpoints) { + try { + const response = await fetch(`http://localhost:8080${endpoint.path}`); + if (response.ok) { + const contentType = response.headers.get('content-type'); + let preview = ''; + if (contentType?.includes('application/json')) { + const data = await response.json(); + preview = JSON.stringify(data).substring(0, 100) + '...'; + } else { + const text = await response.text(); + preview = text.substring(0, 100) + '...'; + } + addLog('success', `${endpoint.description} working`, `Status: ${response.status}, Preview: ${preview}`); + } else { + addLog('warning', `${endpoint.description} returned ${response.status}`); + } + } catch (error) { + addLog('error', `${endpoint.description} failed`, String(error)); + } + } + } + + } catch (error) { + addLog('error', 'Diagnostic process failed', String(error)); + } finally { + setIsRunning(false); + } + }; + + // Auto-run diagnostics on mount + useEffect(() => { + if (autoStart) { + const timer = setTimeout(() => { + runDiagnostics(); + }, 1000); + return () => clearTimeout(timer); + } + }, [autoStart]); + + const getLogColor = (level: DiagnosticResult['level']) => { + switch (level) { + case 'info': return 'text-blue-700 bg-blue-50 border-blue-200'; + case 'warning': return 'text-yellow-700 bg-yellow-50 border-yellow-200'; + case 'error': return 'text-red-700 bg-red-50 border-red-200'; + case 'success': return 'text-green-700 bg-green-50 border-green-200'; + } + }; + + const getLogIcon = (level: DiagnosticResult['level']) => { + switch (level) { + case 'info': return 'â„šī¸'; + case 'warning': return 'âš ī¸'; + case 'error': return '❌'; + case 'success': return '✅'; + } + }; + + return ( +
+
+

Backend Sidecar Diagnostics

+ +
+ + + + + +
+ +
+ {logs.length === 0 ? ( +
+ No diagnostic logs yet. Click "Run Diagnostics" to start. +
+ ) : ( +
+ {logs.map((log, index) => ( +
+
+ {getLogIcon(log.level)} +
+
+ {log.timestamp} + {log.message} +
+ {log.details && ( +
+ {log.details} +
+ )} +
+
+
+ ))} +
+ )} +
+ + {logs.length > 0 && ( +
+ Total logs: {logs.length} | + Errors: {logs.filter(l => l.level === 'error').length} | + Warnings: {logs.filter(l => l.level === 'warning').length} | + Success: {logs.filter(l => l.level === 'success').length} +
+ )} +
+
+ ); +}; \ No newline at end of file diff --git a/frontend/src/services/backendService.ts b/frontend/src/services/backendService.ts new file mode 100644 index 000000000..3dcc7b3d8 --- /dev/null +++ b/frontend/src/services/backendService.ts @@ -0,0 +1,73 @@ +import { invoke } from '@tauri-apps/api/core'; + +export class BackendService { + private static instance: BackendService; + private backendStarted = false; + + static getInstance(): BackendService { + if (!BackendService.instance) { + BackendService.instance = new BackendService(); + } + return BackendService.instance; + } + + async startBackend(): Promise { + if (this.backendStarted) { + return; + } + + try { + const result = await invoke('start_backend'); + console.log('Backend started:', result); + this.backendStarted = true; + + // Wait for backend to be healthy + await this.waitForHealthy(); + } catch (error) { + console.error('Failed to start backend:', error); + throw error; + } + } + + async checkHealth(): Promise { + try { + return await invoke('check_backend_health'); + } catch (error) { + console.error('Health check failed:', error); + return false; + } + } + + private async waitForHealthy(maxAttempts = 30): Promise { + for (let i = 0; i < maxAttempts; i++) { + const isHealthy = await this.checkHealth(); + if (isHealthy) { + console.log('Backend is healthy'); + return; + } + await new Promise(resolve => setTimeout(resolve, 1000)); + } + throw new Error('Backend failed to become healthy after 30 seconds'); + } + + getBackendUrl(): string { + return 'http://localhost:8080'; + } + + async makeApiCall(endpoint: string, options?: RequestInit): Promise { + if (!this.backendStarted) { + await this.startBackend(); + } + + const url = `${this.getBackendUrl()}${endpoint}`; + return fetch(url, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + }); + } +} + +export const backendService = BackendService.getInstance(); \ No newline at end of file