mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Fix: Windows Prod-Server Launch
This commit is contained in:
@@ -100,36 +100,36 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
mkdir -p src-tauri/binaries
|
mkdir -p src-tauri/binaries
|
||||||
JAVAW=$(find suwayomi-extracted -path "*/jre/bin/javaw.exe" | head -1)
|
JAVA=$(find suwayomi-extracted -path "*/jre/bin/java.exe" | head -1)
|
||||||
JAR=$(find suwayomi-extracted -name "Suwayomi-Launcher.jar" | head -1)
|
JAR=$(find suwayomi-extracted -name "Suwayomi-Server.jar" | head -1)
|
||||||
if [ -z "$JAVAW" ]; then
|
if [ -z "$JAVA" ]; then
|
||||||
echo "ERROR: jre/bin/javaw.exe not found. Bundle contents:"
|
echo "ERROR: jre/bin/java.exe not found. Bundle contents:"
|
||||||
find suwayomi-extracted -type f | head -50
|
find suwayomi-extracted -type f | head -50
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ -z "$JAR" ]; then
|
if [ -z "$JAR" ]; then
|
||||||
echo "ERROR: Suwayomi-Launcher.jar not found. Bundle contents:"
|
echo "ERROR: Suwayomi-Server.jar not found. Bundle contents:"
|
||||||
find suwayomi-extracted -type f | head -50
|
find suwayomi-extracted -type f | head -50
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "Found javaw: $JAVAW"
|
echo "Found java: $JAVA"
|
||||||
echo "Found jar: $JAR"
|
echo "Found jar: $JAR"
|
||||||
cp -r suwayomi-extracted src-tauri/binaries/suwayomi-bundle
|
cp -r suwayomi-extracted src-tauri/binaries/suwayomi-bundle
|
||||||
|
|
||||||
- name: Validate staging
|
- name: Validate staging
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
find src-tauri/binaries/suwayomi-bundle -path "*/jre/bin/javaw.exe" \
|
find src-tauri/binaries/suwayomi-bundle -path "*/jre/bin/java.exe" \
|
||||||
| grep -q . || (echo "ERROR: jre/bin/javaw.exe missing" && exit 1)
|
| grep -q . || (echo "ERROR: jre/bin/java.exe missing" && exit 1)
|
||||||
find src-tauri/binaries/suwayomi-bundle -name "Suwayomi-Launcher.jar" \
|
find src-tauri/binaries/suwayomi-bundle -name "Suwayomi-Server.jar" \
|
||||||
| grep -q . || (echo "ERROR: Suwayomi-Launcher.jar missing" && exit 1)
|
| grep -q . || (echo "ERROR: Suwayomi-Server.jar missing" && exit 1)
|
||||||
echo "Staging OK"
|
echo "Staging OK"
|
||||||
|
|
||||||
- name: Patch tauri.conf.json for CI
|
- name: Patch tauri.conf.json for CI
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
sed -i 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
|
sed -i 's/"beforeBuildCommand": "pnpm build"/"beforeBuildCommand": ""/' src-tauri/tauri.conf.json
|
||||||
jq '.bundle.resources = ["binaries/suwayomi-bundle/**"] | .bundle.externalBin = []' \
|
jq '.bundle.resources = ["binaries/suwayomi-bundle/bin/Suwayomi-Server.jar", "binaries/suwayomi-bundle/jre/**/*"] | .bundle.externalBin = []' \
|
||||||
src-tauri/tauri.conf.json > tmp.json && mv tmp.json src-tauri/tauri.conf.json
|
src-tauri/tauri.conf.json > tmp.json && mv tmp.json src-tauri/tauri.conf.json
|
||||||
echo "tauri.conf.json patched:"
|
echo "tauri.conf.json patched:"
|
||||||
cat src-tauri/tauri.conf.json
|
cat src-tauri/tauri.conf.json
|
||||||
|
|||||||
@@ -7,14 +7,7 @@
|
|||||||
"core:default",
|
"core:default",
|
||||||
"shell:allow-open",
|
"shell:allow-open",
|
||||||
"shell:allow-kill",
|
"shell:allow-kill",
|
||||||
{
|
"shell:allow-spawn",
|
||||||
"identifier": "shell:allow-spawn",
|
"shell:allow-execute"
|
||||||
"allow": [
|
|
||||||
{ "name": "java", "args": true },
|
|
||||||
{ "name": "javaw", "args": true },
|
|
||||||
{ "name": "suwayomi-server", "args": true },
|
|
||||||
{ "name": "tachidesk-server", "args": true }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+119
-46
@@ -1,5 +1,6 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use std::io::Write;
|
||||||
use sysinfo::Disks;
|
use sysinfo::Disks;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tauri::{Manager, WindowEvent};
|
use tauri::{Manager, WindowEvent};
|
||||||
@@ -16,13 +17,24 @@ pub struct StorageInfo {
|
|||||||
path: String,
|
path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Debug)]
|
||||||
#[serde(tag = "kind", content = "message")]
|
#[serde(tag = "kind", content = "message")]
|
||||||
pub enum SpawnError {
|
pub enum SpawnError {
|
||||||
NotConfigured(String),
|
NotConfigured(String),
|
||||||
SpawnFailed(String),
|
SpawnFailed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Strip the \\?\ extended-length path prefix that Windows adds to long paths.
|
||||||
|
/// Java and many other tools do not accept this prefix and will fail silently.
|
||||||
|
fn strip_unc(path: PathBuf) -> PathBuf {
|
||||||
|
let s = path.to_string_lossy();
|
||||||
|
if let Some(stripped) = s.strip_prefix(r"\\?\") {
|
||||||
|
PathBuf::from(stripped)
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_downloads_path(downloads_path: &str) -> PathBuf {
|
fn resolve_downloads_path(downloads_path: &str) -> PathBuf {
|
||||||
if !downloads_path.trim().is_empty() {
|
if !downloads_path.trim().is_empty() {
|
||||||
return PathBuf::from(downloads_path);
|
return PathBuf::from(downloads_path);
|
||||||
@@ -181,34 +193,40 @@ fn suwayomi_data_dir() -> PathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ServerInvocation {
|
struct ServerInvocation {
|
||||||
// Absolute path to java/javaw (bundled JRE) or a PATH-resident binary name.
|
|
||||||
// All platforms use app.shell().command() — no externalBin/sidecar needed.
|
|
||||||
bin: String,
|
bin: String,
|
||||||
// Ordered args. rootdir_flag is inserted at position 0 by spawn_server
|
|
||||||
// so -D flags always precede -jar for the JVM.
|
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
// Set to the bundle dir so the jar can resolve its relative lib paths.
|
|
||||||
working_dir: Option<PathBuf>,
|
working_dir: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the platform-appropriate java binary inside a bundled JRE tree,
|
fn find_java_in_bundle(bundle_dir: &PathBuf, log: &mut Option<std::fs::File>) -> Option<PathBuf> {
|
||||||
// or None if the expected path doesn't exist.
|
|
||||||
fn find_java_in_bundle(bundle_dir: &PathBuf) -> Option<PathBuf> {
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let java = bundle_dir.join("jre").join("bin").join("javaw.exe");
|
let java = bundle_dir.join("jre").join("bin").join("java.exe");
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
let java = bundle_dir.join("jre").join("bin").join("java");
|
let java = bundle_dir.join("jre").join("bin").join("java");
|
||||||
|
|
||||||
|
do_log(log, &format!("[find_java] checking path: {:?}", java));
|
||||||
|
do_log(log, &format!("[find_java] exists: {}", java.exists()));
|
||||||
|
|
||||||
if java.exists() { Some(java) } else { None }
|
if java.exists() { Some(java) } else { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn do_log(log: &mut Option<std::fs::File>, msg: &str) {
|
||||||
|
eprintln!("{}", msg);
|
||||||
|
if let Some(f) = log {
|
||||||
|
let _ = writeln!(f, "{}", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_server_binary(
|
fn resolve_server_binary(
|
||||||
binary: &str,
|
binary: &str,
|
||||||
app: &tauri::AppHandle,
|
app: &tauri::AppHandle,
|
||||||
|
log: &mut Option<std::fs::File>,
|
||||||
) -> Result<ServerInvocation, SpawnError> {
|
) -> Result<ServerInvocation, SpawnError> {
|
||||||
// User-supplied explicit path — pass straight through.
|
do_log(log, &format!("[resolve] binary arg = {:?}", binary));
|
||||||
|
|
||||||
if !binary.trim().is_empty() {
|
if !binary.trim().is_empty() {
|
||||||
|
do_log(log, "[resolve] using user-supplied binary path");
|
||||||
return Ok(ServerInvocation {
|
return Ok(ServerInvocation {
|
||||||
bin: binary.to_string(),
|
bin: binary.to_string(),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
@@ -216,27 +234,50 @@ fn resolve_server_binary(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let resource_dir = app
|
let resource_dir = match app.path().resource_dir() {
|
||||||
.path()
|
Ok(p) => {
|
||||||
.resource_dir()
|
let stripped = strip_unc(p);
|
||||||
.map_err(|e| SpawnError::SpawnFailed(format!("resource_dir error: {e}")))?;
|
do_log(log, &format!("[resolve] resource_dir (stripped) = {:?}", stripped));
|
||||||
|
stripped
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let msg = format!("resource_dir error: {e}");
|
||||||
|
do_log(log, &format!("[resolve] ERROR: {}", msg));
|
||||||
|
return Err(SpawnError::SpawnFailed(msg));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ── Windows & Linux: bundled JRE ─────────────────────────────────────────
|
|
||||||
// CI stages the Suwayomi linux-x64 / windows-x64 bundle as a resource at
|
|
||||||
// resource_dir/suwayomi-bundle/ (jar + JRE tree). We invoke the bundled
|
|
||||||
// java binary directly with -jar.
|
|
||||||
//
|
|
||||||
// Final arg order (rootdir_flag prepended by spawn_server):
|
|
||||||
// java -Dsuwayomi...rootDir=<path> -jar Suwayomi-Launcher.jar
|
|
||||||
//
|
|
||||||
// -D flags MUST precede -jar or the JVM silently ignores them.
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
{
|
{
|
||||||
let bundle_dir = resource_dir.join("suwayomi-bundle");
|
// Tauri 2 resource bundling behaviour depends on the config:
|
||||||
let jar = bundle_dir.join("Suwayomi-Launcher.jar");
|
// - Structured layout: resource_dir/binaries/suwayomi-bundle/{bin,jre}/...
|
||||||
|
// - Flat layout: resource_dir/{java.exe,Suwayomi-Server.jar,...}
|
||||||
|
// We try both so the binary works regardless of which layout the installer produced.
|
||||||
|
let search_candidates: &[(&str, &str)] = &[
|
||||||
|
// Structured — what the config intends
|
||||||
|
("binaries/suwayomi-bundle", "binaries/suwayomi-bundle/bin/Suwayomi-Server.jar"),
|
||||||
|
// Flat — what Tauri 2 actually produces with glob resources
|
||||||
|
("", "Suwayomi-Server.jar"),
|
||||||
|
];
|
||||||
|
|
||||||
if let Some(java) = find_java_in_bundle(&bundle_dir) {
|
for (bundle_rel, jar_rel) in search_candidates {
|
||||||
|
let bundle_dir = if bundle_rel.is_empty() {
|
||||||
|
resource_dir.clone()
|
||||||
|
} else {
|
||||||
|
resource_dir.join(bundle_rel)
|
||||||
|
};
|
||||||
|
let jar = resource_dir.join(jar_rel);
|
||||||
|
|
||||||
|
do_log(log, &format!("[resolve] trying bundle_dir = {:?}", bundle_dir));
|
||||||
|
do_log(log, &format!("[resolve] bundle_dir exists: {}", bundle_dir.exists()));
|
||||||
|
do_log(log, &format!("[resolve] jar = {:?}", jar));
|
||||||
|
do_log(log, &format!("[resolve] jar exists: {}", jar.exists()));
|
||||||
|
|
||||||
|
match find_java_in_bundle(&bundle_dir, log) {
|
||||||
|
Some(java) => {
|
||||||
|
do_log(log, &format!("[resolve] java found: {:?}", java));
|
||||||
if jar.exists() {
|
if jar.exists() {
|
||||||
|
do_log(log, "[resolve] both java and jar found — using bundled JRE");
|
||||||
return Ok(ServerInvocation {
|
return Ok(ServerInvocation {
|
||||||
bin: java.to_string_lossy().into_owned(),
|
bin: java.to_string_lossy().into_owned(),
|
||||||
args: vec![
|
args: vec![
|
||||||
@@ -245,14 +286,17 @@ fn resolve_server_binary(
|
|||||||
],
|
],
|
||||||
working_dir: Some(bundle_dir),
|
working_dir: Some(bundle_dir),
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
do_log(log, "[resolve] java found but jar MISSING — trying next candidate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
do_log(log, "[resolve] java NOT found — trying next candidate");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── macOS: bundled launcher script ───────────────────────────────────────
|
|
||||||
// The macOS workflow stages arch-specific .command launcher scripts as
|
|
||||||
// externalBin sidecars. They are self-contained (handle JVM invocation
|
|
||||||
// internally) so we exec the script directly with no extra args.
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
let candidates = [
|
let candidates = [
|
||||||
@@ -262,7 +306,9 @@ fn resolve_server_binary(
|
|||||||
];
|
];
|
||||||
for name in &candidates {
|
for name in &candidates {
|
||||||
let p = resource_dir.join(name);
|
let p = resource_dir.join(name);
|
||||||
|
do_log(log, &format!("[resolve] macOS candidate: {:?} exists={}", p, p.exists()));
|
||||||
if p.exists() {
|
if p.exists() {
|
||||||
|
do_log(log, &format!("[resolve] using macOS candidate: {:?}", p));
|
||||||
return Ok(ServerInvocation {
|
return Ok(ServerInvocation {
|
||||||
bin: p.to_string_lossy().into_owned(),
|
bin: p.to_string_lossy().into_owned(),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
@@ -272,15 +318,7 @@ fn resolve_server_binary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── PATH fallback (all platforms) ────────────────────────────────────────
|
do_log(log, "[resolve] trying PATH fallback");
|
||||||
// Covers:
|
|
||||||
// - nix develop (tachidesk-server in devShell.nativeBuildInputs)
|
|
||||||
// - nix run .#moku (wrapProgram --prefix PATH injects tachidesk-server)
|
|
||||||
// - Distro package installs
|
|
||||||
// - Manual system installs
|
|
||||||
//
|
|
||||||
// The Nix wrapper script accepts "$@" passthrough so the rootdir -D flag
|
|
||||||
// forwarded by spawn_server reaches the underlying JVM correctly.
|
|
||||||
for name in &["suwayomi-server", "tachidesk-server"] {
|
for name in &["suwayomi-server", "tachidesk-server"] {
|
||||||
let found = std::process::Command::new("which")
|
let found = std::process::Command::new("which")
|
||||||
.arg(name)
|
.arg(name)
|
||||||
@@ -288,7 +326,10 @@ fn resolve_server_binary(
|
|||||||
.map(|o| o.status.success())
|
.map(|o| o.status.success())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
do_log(log, &format!("[resolve] PATH check {:?}: found={}", name, found));
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
|
do_log(log, &format!("[resolve] using PATH binary: {}", name));
|
||||||
return Ok(ServerInvocation {
|
return Ok(ServerInvocation {
|
||||||
bin: name.to_string(),
|
bin: name.to_string(),
|
||||||
args: vec![],
|
args: vec![],
|
||||||
@@ -297,6 +338,7 @@ fn resolve_server_binary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do_log(log, "[resolve] FAILED — no binary found anywhere");
|
||||||
Err(SpawnError::NotConfigured(
|
Err(SpawnError::NotConfigured(
|
||||||
"Server binary not found. Install Suwayomi-Server or set the path in Settings.".to_string(),
|
"Server binary not found. Install Suwayomi-Server or set the path in Settings.".to_string(),
|
||||||
))
|
))
|
||||||
@@ -312,37 +354,68 @@ fn spawn_server(binary: String, app: tauri::AppHandle) -> Result<(), SpawnError>
|
|||||||
}
|
}
|
||||||
|
|
||||||
let data_dir = suwayomi_data_dir();
|
let data_dir = suwayomi_data_dir();
|
||||||
|
|
||||||
|
let log_path = data_dir.join("moku-spawn.log");
|
||||||
|
let _ = std::fs::create_dir_all(&data_dir);
|
||||||
|
let mut log = std::fs::OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&log_path)
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
do_log(&mut log, "");
|
||||||
|
do_log(&mut log, "========================================");
|
||||||
|
do_log(&mut log, &format!("[spawn_server] called at {:?}", std::time::SystemTime::now()));
|
||||||
|
do_log(&mut log, &format!("[spawn_server] binary arg = {:?}", binary));
|
||||||
|
do_log(&mut log, &format!("[spawn_server] data_dir = {:?}", data_dir));
|
||||||
|
do_log(&mut log, &format!("[spawn_server] log file = {:?}", log_path));
|
||||||
|
do_log(&mut log, &format!("[spawn_server] APPDATA = {:?}", std::env::var("APPDATA")));
|
||||||
|
do_log(&mut log, &format!("[spawn_server] LOCALAPPDATA = {:?}", std::env::var("LOCALAPPDATA")));
|
||||||
|
do_log(&mut log, &format!("[spawn_server] current_dir = {:?}", std::env::current_dir()));
|
||||||
|
|
||||||
seed_server_conf(&data_dir);
|
seed_server_conf(&data_dir);
|
||||||
|
do_log(&mut log, "[spawn_server] server.conf seeded");
|
||||||
|
|
||||||
|
let mut invocation = match resolve_server_binary(&binary, &app, &mut log) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
do_log(&mut log, &format!("[spawn_server] resolve FAILED: {:?}", e));
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut invocation = resolve_server_binary(&binary, &app)?;
|
|
||||||
let bin_display = invocation.bin.clone();
|
let bin_display = invocation.bin.clone();
|
||||||
|
|
||||||
let rootdir_flag = format!(
|
let rootdir_flag = format!(
|
||||||
"-Dsuwayomi.tachidesk.config.server.rootDir={}",
|
"-Dsuwayomi.tachidesk.config.server.rootDir={}",
|
||||||
data_dir.to_string_lossy()
|
data_dir.to_string_lossy()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Insert rootdir at position 0 so it always precedes -jar for the JVM.
|
|
||||||
// For PATH-resident Nix wrapper scripts the flag is forwarded via "$@".
|
|
||||||
invocation.args.insert(0, rootdir_flag);
|
invocation.args.insert(0, rootdir_flag);
|
||||||
|
|
||||||
let working_dir = invocation.working_dir
|
let working_dir = invocation.working_dir
|
||||||
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
|
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
|
||||||
|
|
||||||
|
do_log(&mut log, &format!("[spawn_server] bin = {:?}", bin_display));
|
||||||
|
do_log(&mut log, &format!("[spawn_server] args = {:?}", invocation.args));
|
||||||
|
do_log(&mut log, &format!("[spawn_server] working_dir = {:?}", working_dir));
|
||||||
|
|
||||||
let cmd = app.shell()
|
let cmd = app.shell()
|
||||||
.command(&invocation.bin)
|
.command(&invocation.bin)
|
||||||
.env("JAVA_TOOL_OPTIONS", "-Djava.awt.headless=true")
|
.env("JAVA_TOOL_OPTIONS", "-Djava.awt.headless=true")
|
||||||
.args(&invocation.args)
|
.args(&invocation.args)
|
||||||
.current_dir(&working_dir);
|
.current_dir(&working_dir);
|
||||||
|
|
||||||
|
do_log(&mut log, "[spawn_server] calling cmd.spawn()...");
|
||||||
|
|
||||||
match cmd.spawn() {
|
match cmd.spawn() {
|
||||||
Ok((_rx, child)) => {
|
Ok((_rx, child)) => {
|
||||||
println!("Spawned server: {}", bin_display);
|
do_log(&mut log, &format!("[spawn_server] SUCCESS — spawned: {}", bin_display));
|
||||||
*app.state::<ServerState>().0.lock().unwrap() = Some(child);
|
*app.state::<ServerState>().0.lock().unwrap() = Some(child);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Failed to spawn {}: {}", bin_display, e);
|
do_log(&mut log, &format!("[spawn_server] SPAWN FAILED: {}", e));
|
||||||
|
do_log(&mut log, &format!("[spawn_server] error kind: {:?}", e));
|
||||||
Err(SpawnError::SpawnFailed(e.to_string()))
|
Err(SpawnError::SpawnFailed(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,11 @@
|
|||||||
"installerIcon": "icons/icon.ico",
|
"installerIcon": "icons/icon.ico",
|
||||||
"installMode": "currentUser"
|
"installMode": "currentUser"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"resources": [
|
||||||
|
"binaries/suwayomi-bundle/bin/Suwayomi-Server.jar",
|
||||||
|
"binaries/suwayomi-bundle/jre/**/*"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"shell": {
|
"shell": {
|
||||||
|
|||||||
+41
-18
@@ -85,22 +85,11 @@
|
|||||||
return () => clearInterval(pollInterval);
|
return () => clearInterval(pollInterval);
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(async () => {
|
// Probe the server in a loop until it responds or we hit MAX_ATTEMPTS.
|
||||||
document.addEventListener("contextmenu", e => e.preventDefault());
|
// Returns a cleanup function that cancels any pending probe.
|
||||||
(window as any).__mokuShowSplash = () => devSplash = true;
|
function startProbe(): () => void {
|
||||||
|
|
||||||
if (store.settings.autoStartServer) {
|
|
||||||
invoke<void>("spawn_server", { binary: store.settings.serverBinary }).catch((err: any) => {
|
|
||||||
if (err?.kind === "NotConfigured") {
|
|
||||||
notConfigured = true;
|
|
||||||
} else {
|
|
||||||
console.warn("Could not start server:", err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serverProbeOk) {
|
|
||||||
let cancelled = false, tries = 0;
|
let cancelled = false, tries = 0;
|
||||||
|
|
||||||
async function probe() {
|
async function probe() {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
tries++;
|
tries++;
|
||||||
@@ -115,14 +104,42 @@
|
|||||||
if (tries >= MAX_ATTEMPTS && !cancelled) { failed = true; return; }
|
if (tries >= MAX_ATTEMPTS && !cancelled) { failed = true; return; }
|
||||||
if (!cancelled) setTimeout(probe, 800);
|
if (!cancelled) setTimeout(probe, 800);
|
||||||
}
|
}
|
||||||
setTimeout(probe, 800);
|
|
||||||
|
// Give the server a moment to start binding its port before the first probe.
|
||||||
|
setTimeout(probe, 1200);
|
||||||
|
return () => { cancelled = true; };
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
document.addEventListener("contextmenu", e => e.preventDefault());
|
||||||
|
(window as any).__mokuShowSplash = () => devSplash = true;
|
||||||
|
|
||||||
|
let cancelProbe = () => {};
|
||||||
|
|
||||||
|
if (store.settings.autoStartServer) {
|
||||||
|
try {
|
||||||
|
await invoke<void>("spawn_server", { binary: store.settings.serverBinary ?? "" });
|
||||||
|
// spawn_server succeeded — JRE found and process started. Begin probing.
|
||||||
|
cancelProbe = startProbe();
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err?.kind === "NotConfigured") {
|
||||||
|
notConfigured = true;
|
||||||
|
} else {
|
||||||
|
// SpawnFailed — process couldn't be launched (permissions, bad path, etc.)
|
||||||
|
console.error("spawn_server failed:", err);
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// autoStartServer is off — user manages the server themselves, just probe.
|
||||||
|
cancelProbe = startProbe();
|
||||||
}
|
}
|
||||||
|
|
||||||
type P = { chapterId: number; mangaId: number; progress: number }[];
|
type P = { chapterId: number; mangaId: number; progress: number }[];
|
||||||
unlistenDownload = await listen<P>("download-progress", e => { setActiveDownloads(e.payload); });
|
unlistenDownload = await listen<P>("download-progress", e => { setActiveDownloads(e.payload); });
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelProbe();
|
||||||
if (store.settings.autoStartServer) invoke("kill_server").catch(() => {});
|
if (store.settings.autoStartServer) invoke("kill_server").catch(() => {});
|
||||||
if (idleTimer) clearTimeout(idleTimer);
|
if (idleTimer) clearTimeout(idleTimer);
|
||||||
if (pollInterval) clearInterval(pollInterval);
|
if (pollInterval) clearInterval(pollInterval);
|
||||||
@@ -131,7 +148,13 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleRetry() { failed = false; notConfigured = false; serverProbeOk = false; }
|
function handleRetry() {
|
||||||
|
failed = false;
|
||||||
|
notConfigured = false;
|
||||||
|
serverProbeOk = false;
|
||||||
|
// Re-run the full startup flow by reloading — simplest way to reset all state cleanly.
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if devSplash}
|
{#if devSplash}
|
||||||
|
|||||||
Reference in New Issue
Block a user