fix(core): show processing state during automatic startup
- track automatic core startup in shared runtime state - expose the starting state through CoreStatus - disable core controls while startup is in progress - clear the processing state after startup succeeds or fails - add regression tests for startup state handling
This commit is contained in:
1 parent
30fb184384
commit
dbd97d862f
6 files changed
+54
-10
No files matched your search
@@ -802,6 +802,7 @@ pub(crate) fn current_core_status(
|
||||
let install_dir = core_install_dir()?;
|
||||
let binary_path = find_core_binary(&install_dir);
|
||||
let installed = binary_path.is_some();
|
||||
let starting = process_state.is_some_and(CoreProcessState::is_starting);
|
||||
let managed_pid = process_state.and_then(|state| state.managed_pid());
|
||||
let management_port_open = management_port.map(is_management_port_open);
|
||||
let process_id = match managed_pid {
|
||||
@@ -814,7 +815,9 @@ pub(crate) fn current_core_status(
|
||||
let running = process_id.is_some() && management_port_open.unwrap_or(true);
|
||||
let current_version = read_core_metadata(&install_dir).map(|metadata| metadata.version);
|
||||
|
||||
let message = if !installed {
|
||||
let message = if starting {
|
||||
"CPA 内核正在启动".to_string()
|
||||
} else if !installed {
|
||||
"未安装 CPA 内核,请先安装最新版".to_string()
|
||||
} else if running {
|
||||
"CPA 内核正在运行".to_string()
|
||||
@@ -825,6 +828,7 @@ pub(crate) fn current_core_status(
|
||||
Ok(CoreStatus {
|
||||
installed,
|
||||
running,
|
||||
starting,
|
||||
managed: managed_pid.is_some(),
|
||||
process_id,
|
||||
current_version,
|
||||
|
||||
+31
-2
@@ -39,7 +39,6 @@ use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::NSEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::sync::Arc;
|
||||
@@ -276,6 +275,7 @@ struct AppUpdateInner {
|
||||
#[derive(Default)]
|
||||
struct CoreProcessState {
|
||||
child: Mutex<Option<Child>>,
|
||||
starting: AtomicBool,
|
||||
#[cfg(windows)]
|
||||
job: Mutex<Option<isize>>,
|
||||
}
|
||||
@@ -370,6 +370,7 @@ struct CorePlatform {
|
||||
struct CoreStatus {
|
||||
installed: bool,
|
||||
running: bool,
|
||||
starting: bool,
|
||||
managed: bool,
|
||||
process_id: Option<u32>,
|
||||
current_version: Option<String>,
|
||||
@@ -1341,6 +1342,23 @@ impl AppUpdateState {
|
||||
}
|
||||
|
||||
impl CoreProcessState {
|
||||
fn new(starting: bool) -> Self {
|
||||
Self {
|
||||
child: Mutex::new(None),
|
||||
starting: AtomicBool::new(starting),
|
||||
#[cfg(windows)]
|
||||
job: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_starting(&self) -> bool {
|
||||
self.starting.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
fn set_starting(&self, starting: bool) {
|
||||
self.starting.store(starting, Ordering::Release);
|
||||
}
|
||||
|
||||
fn managed_pid(&self) -> Option<u32> {
|
||||
let Ok(mut child) = self.child.lock() else {
|
||||
return None;
|
||||
@@ -1727,7 +1745,7 @@ fn main() {
|
||||
)
|
||||
.manage(CoreDownloadState::default())
|
||||
.manage(AppUpdateState::default())
|
||||
.manage(CoreProcessState::default())
|
||||
.manage(CoreProcessState::new(gui_config.start_core_on_launch))
|
||||
.manage(usage::UsageCollectorState::default())
|
||||
.manage(GuiConfigState::new(gui_config))
|
||||
.manage(MainWindowSizeState::new(initial_window_size))
|
||||
@@ -1845,9 +1863,18 @@ fn main() {
|
||||
let gui_config_state = core_app.state::<GuiConfigState>();
|
||||
let process_state = core_app.state::<CoreProcessState>();
|
||||
let Ok(config) = gui_config_state.snapshot() else {
|
||||
process_state.set_starting(false);
|
||||
return;
|
||||
};
|
||||
|
||||
if config.start_core_on_launch {
|
||||
if let Ok(status) =
|
||||
current_core_status(Some(process_state.inner()), Some(config.port))
|
||||
{
|
||||
emit_core_status(&core_app, &status);
|
||||
}
|
||||
}
|
||||
|
||||
match auto_install_bundled_core_if_missing(&core_app) {
|
||||
Ok(true) => eprintln!("未检测到 CPA 内核,已自动安装内置离线版本"),
|
||||
Ok(false) => {}
|
||||
@@ -1860,6 +1887,8 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
process_state.set_starting(false);
|
||||
|
||||
if let Ok(status) =
|
||||
current_core_status(Some(process_state.inner()), Some(config.port))
|
||||
{
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
use super::support::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn core_process_starting_state_tracks_automatic_launch() {
|
||||
let state = CoreProcessState::new(true);
|
||||
assert!(state.is_starting());
|
||||
state.set_starting(false);
|
||||
assert!(!state.is_starting());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replacing_a_core_preserves_only_regular_bundled_assets() {
|
||||
let root = agent_test_home("bundled-assets");
|
||||
|
||||
@@ -6,6 +6,7 @@ fn windows_tray_presentation_tracks_core_state_and_busy_actions() {
|
||||
let mut status = CoreStatus {
|
||||
installed: false,
|
||||
running: false,
|
||||
starting: false,
|
||||
managed: false,
|
||||
process_id: None,
|
||||
current_version: None,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { listen } from '@tauri-apps/api/event';
|
||||
export type CoreStatus = {
|
||||
installed: boolean;
|
||||
running: boolean;
|
||||
starting: boolean;
|
||||
managed: boolean;
|
||||
processId: number | null;
|
||||
currentVersion: string | null;
|
||||
|
||||
@@ -584,9 +584,10 @@ export function KernelPage({ view = 'home' }: { view?: KernelView }) {
|
||||
const currentVersion = coreStatus?.currentVersion ?? '';
|
||||
const coreInstalled = Boolean(coreStatus?.installed);
|
||||
const coreRunning = Boolean(coreStatus?.running);
|
||||
const busy = checkingLatest || installing || processBusy;
|
||||
const coreProcessBusy = processBusy || Boolean(coreStatus?.starting);
|
||||
const busy = checkingLatest || installing || coreProcessBusy;
|
||||
const installDisabled = busy || Boolean(coreStatus?.running);
|
||||
const offlineInstallDisabled = installing || processBusy || coreRunning;
|
||||
const offlineInstallDisabled = installing || coreProcessBusy || coreRunning;
|
||||
const computedPercent =
|
||||
progress?.percent ??
|
||||
(progress?.total && progress.total > 0 ? (progress.downloaded / progress.total) * 100 : null);
|
||||
@@ -702,7 +703,7 @@ export function KernelPage({ view = 'home' }: { view?: KernelView }) {
|
||||
<h2>{t('kernel.control.title')}</h2>
|
||||
</div>
|
||||
<span className={`state-pill ${statusTone}`} title={statusError || undefined}>
|
||||
{processBusy ? t('common.processing') : statusLabel}
|
||||
{coreProcessBusy ? t('common.processing') : statusLabel}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -736,7 +737,7 @@ export function KernelPage({ view = 'home' }: { view?: KernelView }) {
|
||||
<button
|
||||
type="button"
|
||||
className={coreRunning ? 'danger-button' : 'primary-button'}
|
||||
disabled={!coreInstalled || installing || processBusy}
|
||||
disabled={!coreInstalled || installing || coreProcessBusy}
|
||||
onClick={() =>
|
||||
void runCoreProcessCommand(
|
||||
coreRunning ? 'stop_core_process' : 'start_core_process',
|
||||
@@ -744,12 +745,12 @@ export function KernelPage({ view = 'home' }: { view?: KernelView }) {
|
||||
)
|
||||
}
|
||||
>
|
||||
{processBusy ? t('common.processing') : coreRunning ? t('kernel.action.stop') : t('kernel.action.start')}
|
||||
{coreProcessBusy ? t('common.processing') : coreRunning ? t('kernel.action.stop') : t('kernel.action.start')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-button"
|
||||
disabled={!coreInstalled || !coreRunning || installing || processBusy}
|
||||
disabled={!coreInstalled || !coreRunning || installing || coreProcessBusy}
|
||||
onClick={() =>
|
||||
void runCoreProcessCommand('restart_core_process', { success: t('kernel.notice.restarted') })
|
||||
}
|
||||
@@ -759,7 +760,7 @@ export function KernelPage({ view = 'home' }: { view?: KernelView }) {
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-button"
|
||||
disabled={processBusy}
|
||||
disabled={coreProcessBusy}
|
||||
onClick={() => void refreshStatus()}
|
||||
>
|
||||
{t('kernel.control.refresh')}
|
||||
|
||||
Reference in new issue
Block a user