mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 01:09:56 -05:00
Fix: Cache-Boot (KCEF Corruption)
This commit is contained in:
@@ -75,6 +75,24 @@ fn remove_dir_best_effort(path: &std::path::Path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wait_until_deletable(path: &std::path::Path, timeout_secs: u64) -> bool {
|
||||||
|
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(timeout_secs);
|
||||||
|
while std::time::Instant::now() < deadline {
|
||||||
|
let locked = if path.is_file() {
|
||||||
|
std::fs::OpenOptions::new().write(true).open(path).is_err()
|
||||||
|
} else if path.is_dir() {
|
||||||
|
std::fs::read_dir(path).is_err()
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if !locked {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn clear_moku_cache(app: tauri::AppHandle) -> Result<(), String> {
|
pub async fn clear_moku_cache(app: tauri::AppHandle) -> Result<(), String> {
|
||||||
let window = app.get_webview_window("main").ok_or("no main window")?;
|
let window = app.get_webview_window("main").ok_or("no main window")?;
|
||||||
@@ -92,10 +110,17 @@ pub async fn clear_moku_cache(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
pub fn clear_suwayomi_cache() -> Result<(), String> {
|
pub fn clear_suwayomi_cache() -> Result<(), String> {
|
||||||
use crate::server::resolve::suwayomi_data_dir;
|
use crate::server::resolve::suwayomi_data_dir;
|
||||||
let data_dir = suwayomi_data_dir();
|
let data_dir = suwayomi_data_dir();
|
||||||
for dir in &["cache", "bin/kcef", "cache/kcef"] {
|
for dir in &["cache/kcef", "logs"] {
|
||||||
let p = data_dir.join(dir);
|
let p = data_dir.join(dir);
|
||||||
if p.exists() {
|
if p.exists() {
|
||||||
std::fs::remove_dir_all(&p).map_err(|e| e.to_string())?;
|
remove_dir_best_effort(&p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for dir in &["downloads/thumbnails"] {
|
||||||
|
let p = data_dir.join(dir);
|
||||||
|
if p.exists() {
|
||||||
|
remove_dir_best_effort(&p);
|
||||||
|
let _ = std::fs::create_dir_all(&p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -106,10 +131,19 @@ pub fn reset_suwayomi_data(app: tauri::AppHandle) -> Result<(), String> {
|
|||||||
use crate::server::resolve::suwayomi_data_dir;
|
use crate::server::resolve::suwayomi_data_dir;
|
||||||
|
|
||||||
crate::server::kill_tachidesk(&app);
|
crate::server::kill_tachidesk(&app);
|
||||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
|
||||||
|
|
||||||
let data_dir = suwayomi_data_dir();
|
let data_dir = suwayomi_data_dir();
|
||||||
for entry_name in &["database.mv.db", "extensions", "settings", "logs", "local"] {
|
let targets = ["database.mv.db", "extensions", "settings", "logs", "local"];
|
||||||
|
|
||||||
|
// Wait up to 10s for the JVM to release file locks
|
||||||
|
for entry_name in &targets {
|
||||||
|
let p = data_dir.join(entry_name);
|
||||||
|
if p.exists() {
|
||||||
|
wait_until_deletable(&p, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry_name in &targets {
|
||||||
let p = data_dir.join(entry_name);
|
let p = data_dir.join(entry_name);
|
||||||
if p.is_dir() {
|
if p.is_dir() {
|
||||||
std::fs::remove_dir_all(&p).map_err(|e| format!("{entry_name}: {e}"))?;
|
std::fs::remove_dir_all(&p).map_err(|e| format!("{entry_name}: {e}"))?;
|
||||||
|
|||||||
+10
-8
@@ -91,6 +91,13 @@
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!appReady) return;
|
||||||
|
downloadStore.poll();
|
||||||
|
const dlInterval = setInterval(() => downloadStore.poll(), 2000);
|
||||||
|
return () => clearInterval(dlInterval);
|
||||||
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (store.settings.discordRpc) {
|
if (store.settings.discordRpc) {
|
||||||
initRpc();
|
initRpc();
|
||||||
@@ -125,9 +132,11 @@
|
|||||||
applyZoom();
|
applyZoom();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const unlistenClose = await win.listen("tauri://close-requested", handleCloseRequested);
|
const unlistenClose = await win.listen("tauri://close-requested", handleCloseRequested);
|
||||||
|
|
||||||
|
await initStore();
|
||||||
|
startProbe();
|
||||||
|
|
||||||
if (store.settings.autoStartServer) {
|
if (store.settings.autoStartServer) {
|
||||||
invoke<void>("spawn_server", { binary: store.settings.serverBinary }).catch((err: any) => {
|
invoke<void>("spawn_server", { binary: store.settings.serverBinary }).catch((err: any) => {
|
||||||
if (err?.kind === "NotConfigured") boot.notConfigured = true;
|
if (err?.kind === "NotConfigured") boot.notConfigured = true;
|
||||||
@@ -135,20 +144,13 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await initStore();
|
|
||||||
startProbe();
|
|
||||||
|
|
||||||
const unlistenDownload = await listen<{ chapterId: number; mangaId: number; progress: number }[]>(
|
const unlistenDownload = await listen<{ chapterId: number; mangaId: number; progress: number }[]>(
|
||||||
"download-progress",
|
"download-progress",
|
||||||
e => setActiveDownloads(e.payload),
|
e => setActiveDownloads(e.payload),
|
||||||
);
|
);
|
||||||
|
|
||||||
await downloadStore.poll();
|
|
||||||
const dlInterval = setInterval(() => downloadStore.poll(), 2000);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stopProbe();
|
stopProbe();
|
||||||
clearInterval(dlInterval);
|
|
||||||
unlistenResize();
|
unlistenResize();
|
||||||
unlistenScale();
|
unlistenScale();
|
||||||
unlistenDownload();
|
unlistenDownload();
|
||||||
|
|||||||
@@ -559,15 +559,15 @@
|
|||||||
.page-loader-single {
|
.page-loader-single {
|
||||||
width: min(100%, var(--effective-width, 100%));
|
width: min(100%, var(--effective-width, 100%));
|
||||||
max-width: var(--effective-width, 100%);
|
max-width: var(--effective-width, 100%);
|
||||||
max-height: calc(100vh - 80px);
|
max-height: calc(var(--visual-vh, 100vh) - 80px);
|
||||||
aspect-ratio: 2 / 3;
|
aspect-ratio: 2 / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.img { display: block; user-select: none; image-rendering: auto; }
|
.img { display: block; user-select: none; image-rendering: auto; }
|
||||||
.img:global(.optimize-contrast) { image-rendering: -webkit-optimize-contrast; }
|
.img:global(.optimize-contrast) { image-rendering: -webkit-optimize-contrast; }
|
||||||
:global(.fit-width) { max-width: var(--effective-width, 100%); width: 100%; height: auto; }
|
:global(.fit-width) { max-width: var(--effective-width, 100%); width: 100%; height: auto; }
|
||||||
:global(.fit-height) { max-height: calc(100vh - 80px); width: auto; max-width: var(--effective-width, 100%); height: auto; }
|
:global(.fit-height) { max-height: calc(var(--visual-vh, 100vh) - 80px); width: auto; max-width: var(--effective-width, 100%); height: auto; }
|
||||||
:global(.fit-screen) { max-width: var(--effective-width, 100%); max-height: calc(100vh - 80px); object-fit: contain; height: auto; }
|
:global(.fit-screen) { max-width: var(--effective-width, 100%); max-height: calc(var(--visual-vh, 100vh) - 80px); object-fit: contain; height: auto; }
|
||||||
:global(.fit-original) { max-width: 100%; width: auto; height: auto; }
|
:global(.fit-original) { max-width: 100%; width: auto; height: auto; }
|
||||||
:global(.strip-gap) { margin-bottom: 8px; }
|
:global(.strip-gap) { margin-bottom: 8px; }
|
||||||
|
|
||||||
|
|||||||
+66
-43
@@ -4,7 +4,8 @@ import { trackingState } from "@features/tracking/store/tracki
|
|||||||
import { loadAllStores } from "@core/persistence/persist";
|
import { loadAllStores } from "@core/persistence/persist";
|
||||||
import { notifyReauthSuccess } from "@api/client";
|
import { notifyReauthSuccess } from "@api/client";
|
||||||
|
|
||||||
const MAX_ATTEMPTS = 40;
|
const MAX_ATTEMPTS = 15;
|
||||||
|
const BG_MAX_ATTEMPTS = 60;
|
||||||
|
|
||||||
export const boot = $state({
|
export const boot = $state({
|
||||||
serverProbeOk: false,
|
serverProbeOk: false,
|
||||||
@@ -26,6 +27,44 @@ export async function initStore() {
|
|||||||
store.hydrate(saved);
|
store.hydrate(saved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleProbeSuccess(gen: number) {
|
||||||
|
if (gen !== probeGeneration) return;
|
||||||
|
boot.serverProbeOk = true;
|
||||||
|
boot.failed = false;
|
||||||
|
boot.skipped = false;
|
||||||
|
trackingState.bootSync().catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAuthRequired(gen: number) {
|
||||||
|
if (gen !== probeGeneration) return;
|
||||||
|
boot.serverProbeOk = true;
|
||||||
|
boot.failed = false;
|
||||||
|
const mode = store.settings.serverAuthMode ?? "NONE";
|
||||||
|
if (mode === "BASIC_AUTH") {
|
||||||
|
const user = store.settings.serverAuthUser?.trim() ?? "";
|
||||||
|
const pass = store.settings.serverAuthPass?.trim() ?? "";
|
||||||
|
if (user && pass) {
|
||||||
|
loginBasic(user, pass)
|
||||||
|
.then(() => { if (gen === probeGeneration) trackingState.bootSync().catch(() => {}); })
|
||||||
|
.catch(() => {
|
||||||
|
if (gen !== probeGeneration) return;
|
||||||
|
boot.loginUser = store.settings.serverAuthUser ?? "";
|
||||||
|
boot.loginRequired = true;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boot.loginUser = store.settings.serverAuthUser ?? "";
|
||||||
|
boot.loginRequired = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mode === "UI_LOGIN") {
|
||||||
|
boot.loginUser = store.settings.serverAuthUser ?? "";
|
||||||
|
boot.loginRequired = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trackingState.bootSync().catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
export function startProbe() {
|
export function startProbe() {
|
||||||
const gen = ++probeGeneration;
|
const gen = ++probeGeneration;
|
||||||
boot.failed = false;
|
boot.failed = false;
|
||||||
@@ -36,51 +75,36 @@ export function startProbe() {
|
|||||||
async function probe() {
|
async function probe() {
|
||||||
if (gen !== probeGeneration) return;
|
if (gen !== probeGeneration) return;
|
||||||
tries++;
|
tries++;
|
||||||
|
|
||||||
const result = await probeServer();
|
const result = await probeServer();
|
||||||
if (gen !== probeGeneration) return;
|
if (gen !== probeGeneration) return;
|
||||||
|
|
||||||
if (result === "ok") {
|
if (result === "ok") { handleProbeSuccess(gen); return; }
|
||||||
boot.serverProbeOk = true;
|
if (result === "auth_required") { handleAuthRequired(gen); return; }
|
||||||
trackingState.bootSync().catch(() => {});
|
if (tries >= MAX_ATTEMPTS) { boot.failed = true; startBackgroundProbe(gen); return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result === "auth_required") {
|
setTimeout(probe, Math.min(300 + tries * 150, 1500));
|
||||||
boot.serverProbeOk = true;
|
|
||||||
const mode = store.settings.serverAuthMode ?? "NONE";
|
|
||||||
|
|
||||||
if (mode === "BASIC_AUTH") {
|
|
||||||
const user = store.settings.serverAuthUser?.trim() ?? "";
|
|
||||||
const pass = store.settings.serverAuthPass?.trim() ?? "";
|
|
||||||
if (user && pass) {
|
|
||||||
try {
|
|
||||||
await loginBasic(user, pass);
|
|
||||||
if (gen !== probeGeneration) return;
|
|
||||||
trackingState.bootSync().catch(() => {});
|
|
||||||
return;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
boot.loginUser = store.settings.serverAuthUser ?? "";
|
|
||||||
boot.loginRequired = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === "UI_LOGIN") {
|
|
||||||
boot.loginUser = store.settings.serverAuthUser ?? "";
|
|
||||||
boot.loginRequired = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
trackingState.bootSync().catch(() => {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tries >= MAX_ATTEMPTS) { boot.failed = true; return; }
|
|
||||||
setTimeout(probe, Math.min(750 + tries * 250, 3000));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(probe, 2000);
|
setTimeout(probe, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startBackgroundProbe(gen: number) {
|
||||||
|
let bgTries = 0;
|
||||||
|
|
||||||
|
async function bgProbe() {
|
||||||
|
if (gen !== probeGeneration) return;
|
||||||
|
bgTries++;
|
||||||
|
const result = await probeServer();
|
||||||
|
if (gen !== probeGeneration) return;
|
||||||
|
|
||||||
|
if (result === "ok") { handleProbeSuccess(gen); return; }
|
||||||
|
if (result === "auth_required") { handleAuthRequired(gen); return; }
|
||||||
|
if (bgTries >= BG_MAX_ATTEMPTS) return;
|
||||||
|
|
||||||
|
setTimeout(bgProbe, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(bgProbe, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopProbe() {
|
export function stopProbe() {
|
||||||
@@ -94,7 +118,6 @@ export async function submitLogin(onSuccess: () => void): Promise<void> {
|
|||||||
}
|
}
|
||||||
boot.loginBusy = true;
|
boot.loginBusy = true;
|
||||||
boot.loginError = null;
|
boot.loginError = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mode = store.settings.serverAuthMode ?? "NONE";
|
const mode = store.settings.serverAuthMode ?? "NONE";
|
||||||
if (mode === "UI_LOGIN") {
|
if (mode === "UI_LOGIN") {
|
||||||
@@ -102,7 +125,6 @@ export async function submitLogin(onSuccess: () => void): Promise<void> {
|
|||||||
} else {
|
} else {
|
||||||
await loginBasic(boot.loginUser.trim(), boot.loginPass.trim());
|
await loginBasic(boot.loginUser.trim(), boot.loginPass.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
boot.loginRequired = false;
|
boot.loginRequired = false;
|
||||||
boot.sessionExpired = false;
|
boot.sessionExpired = false;
|
||||||
boot.skipped = false;
|
boot.skipped = false;
|
||||||
@@ -128,10 +150,11 @@ export function retryBoot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function bypassBoot(onReady: () => void) {
|
export function bypassBoot(onReady: () => void) {
|
||||||
probeGeneration++;
|
const gen = probeGeneration;
|
||||||
boot.serverProbeOk = true;
|
boot.serverProbeOk = true;
|
||||||
boot.loginRequired = false;
|
boot.loginRequired = false;
|
||||||
boot.sessionExpired = false;
|
boot.sessionExpired = false;
|
||||||
boot.skipped = true;
|
boot.skipped = true;
|
||||||
onReady();
|
onReady();
|
||||||
|
startBackgroundProbe(gen);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user