Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions openless-all/app/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ macro_rules! app_invoke_handler_desktop {
commands::sherpa_onnx_asr_reveal_model_dir,
commands::export_error_log,
restart_app,
log_client_error,
set_windows_caption_theme,
]
};
Expand Down Expand Up @@ -378,6 +379,7 @@ macro_rules! app_invoke_handler_mobile {
$crate::commands::app_check_update_with_channel,
$crate::commands::app_download_and_install_android_update,
$crate::restart_app,
$crate::log_client_error,
]
};
}
Expand Down Expand Up @@ -1140,6 +1142,26 @@ fn restart_app(app: AppHandle) {
app.restart();
}

/// 把前端的关键错误(如自动更新 install 失败)转发到 Rust 文件日志(openless.log)。
/// webview 的 console.error 不会进 openless.log,单独留一个 IPC,便于用户「导出日志」
/// 后我们拿到自动更新失败的真实原因。
#[tauri::command]
fn log_client_error(message: String) {
// message 由前端 webview 可控,可能很长或含换行(伪造日志行)。先把换行折成空格、
// 再按 UTF-8 字符边界截断,避免单条日志过大或污染日志格式。
const MAX_LEN: usize = 2048;
let mut sanitized = message.replace(['\n', '\r'], " ");
if sanitized.len() > MAX_LEN {
let mut end = MAX_LEN;
while !sanitized.is_char_boundary(end) {
end -= 1;
}
sanitized.truncate(end);
sanitized.push_str("…(truncated)");
}
log::error!("[client] {sanitized}");
}

#[cfg(target_os = "macos")]
fn reset_tcc_for_beta_restart() {
if !is_beta_build() {
Expand Down
35 changes: 27 additions & 8 deletions openless-all/app/src/components/AutoUpdate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
appDownloadAndInstallAndroidUpdate,
isAndroid,
isTauri,
logClientError,
openExternal,
restartApp,
type AppUpdateMetadata,
type UpdateChannel,
Expand All @@ -23,6 +25,9 @@ import { Btn } from '../pages/_atoms';

const UPDATE_CHECK_TIMEOUT_MS = 15_000;

// 自动更新失败时的手动下载兜底:直达 GitHub Releases(与「关于」页 RELEASE_NOTES_URL 一致)。
const RELEASE_DOWNLOAD_URL = 'https://github.com/appergb/openless/releases';

export type UpdateStatus =
| 'idle'
| 'checking'
Expand All @@ -31,7 +36,11 @@ export type UpdateStatus =
| 'downloading'
| 'installing'
| 'downloaded'
| 'error';
| 'error'
// installError:下载/安装这一步失败(区别于 'error' 的「检查失败」)。
// 检查失败沿用 'error',在 CheckUpdateButton 里只做按钮内轻提示、不弹框,
// 后台自动检查失败也不弹框;只有 installError 才让弹框留在原地显示错误 + 手动下载兜底。
| 'installError';

export interface UseAutoUpdate {
status: UpdateStatus;
Expand Down Expand Up @@ -162,6 +171,7 @@ export function useAutoUpdate(): UseAutoUpdate {
} catch (error) {
console.error('[updater] failed to check update', error);
const msg = error instanceof Error ? error.message : String(error);
void logClientError(`[updater] check failed: ${msg}`);
setErrorMessage(msg);
setStatus('error');
}
Expand All @@ -180,8 +190,9 @@ export function useAutoUpdate(): UseAutoUpdate {
} catch (error) {
console.error('[updater] failed to install android update', error);
const msg = error instanceof Error ? error.message : String(error);
void logClientError(`[updater] android install failed (v${payload.version}): ${msg}`);
setErrorMessage(msg);
setStatus('error');
setStatus('installError');
}
return;
}
Expand All @@ -208,9 +219,10 @@ export function useAutoUpdate(): UseAutoUpdate {
} catch (error) {
console.error('[updater] failed to install update', error);
const msg = error instanceof Error ? error.message : String(error);
void logClientError(`[updater] install failed (v${update.version}): ${msg}`);
setErrorMessage(msg);
await closeUpdate();
setStatus('error');
setStatus('installError');
}
};

Expand All @@ -237,8 +249,8 @@ export function useAutoUpdate(): UseAutoUpdate {
};
}

export function isDialogStatus(status: UpdateStatus): status is 'available' | 'downloading' | 'installing' | 'downloaded' {
return status === 'available' || status === 'downloading' || status === 'installing' || status === 'downloaded';
export function isDialogStatus(status: UpdateStatus): status is 'available' | 'downloading' | 'installing' | 'downloaded' | 'installError' {
return status === 'available' || status === 'downloading' || status === 'installing' || status === 'downloaded' || status === 'installError';
}

export function UpdateDialog({
Expand All @@ -247,29 +259,34 @@ export function UpdateDialog({
progress,
downloaded,
contentLength,
errorMessage,
onInstall,
onClose,
}: {
status: 'available' | 'downloading' | 'installing' | 'downloaded';
status: 'available' | 'downloading' | 'installing' | 'downloaded' | 'installError';
version: string;
progress: number | null;
downloaded: number;
contentLength: number | null;
errorMessage?: string | null;
onInstall: () => void;
onClose: () => void;
}) {
const { t } = useTranslation();
const downloading = status === 'downloading';
const installing = status === 'installing';
const installError = status === 'installError';
const androidInstalled = isAndroid() && status === 'downloaded';
return (
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.18)', display: 'grid', placeItems: 'center', zIndex: 40 }}>
<div style={{ width: 360, borderRadius: 16, background: 'var(--ol-surface)', border: '0.5px solid var(--ol-line-strong)', boxShadow: '0 18px 42px rgba(0,0,0,0.18)', padding: 18 }}>
<div style={{ fontSize: 15, fontWeight: 650, marginBottom: 8 }}>{t(`settings.about.updateDialog.${status}.title`)}</div>
<div style={{ fontSize: 12, color: 'var(--ol-ink-3)', lineHeight: 1.6, marginBottom: 14 }}>
<div style={{ fontSize: 12, color: 'var(--ol-ink-3)', lineHeight: 1.6, marginBottom: 14, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
{androidInstalled
? t('settings.about.updateDialog.androidInstalled.desc', { version, defaultValue: '系统安装器已打开,请按提示完成安装。安装后重新打开 OpenLess 即可使用 {{version}}。' })
: t(`settings.about.updateDialog.${status}.desc`, { version })}
: installError
? t('settings.about.updateDialog.installError.desc', { error: errorMessage || t('settings.about.updateError') })
: t(`settings.about.updateDialog.${status}.desc`, { version })}
</div>
{(downloading || installing || status === 'downloaded') && (
<div style={{ marginBottom: 14 }}>
Expand All @@ -291,6 +308,8 @@ export function UpdateDialog({
{(downloading || installing) && <Btn size="sm" disabled>{installing ? t('settings.about.updateDialog.installingLabel') : t('settings.about.updateDialog.downloadingLabel')}</Btn>}
{status === 'downloaded' && <Btn size="sm" onClick={onClose}>{t('settings.about.updateDialog.later')}</Btn>}
{status === 'downloaded' && !androidInstalled && <Btn variant="blue" size="sm" onClick={restartApp}>{t('settings.about.updateDialog.restartNow')}</Btn>}
{installError && <Btn size="sm" onClick={onClose}>{t('common.cancel')}</Btn>}
{installError && <Btn variant="blue" size="sm" onClick={() => void openExternal(RELEASE_DOWNLOAD_URL)}>{t('settings.about.updateDialog.manualDownload')}</Btn>}
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/src/components/AutoUpdateGate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function AutoUpdateGate() {
progress={u.progress}
downloaded={u.downloaded}
contentLength={u.contentLength}
errorMessage={u.errorMessage}
onInstall={u.installUpdate}
onClose={u.dismissDialog}
/>
Expand Down
5 changes: 5 additions & 0 deletions openless-all/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,11 @@ export const en: typeof zhCN = {
restartNow: 'Restart now',
progress: '{{progress}}% · {{downloaded}} / {{total}}',
progressUnknown: '{{downloaded}} downloaded',
installError: {
title: 'Update failed',
desc: "The automatic update couldn't finish: {{error}}. You can download and install the latest version manually.",
},
manualDownload: 'Download manually',
},
},
},
Expand Down
5 changes: 5 additions & 0 deletions openless-all/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,11 @@ export const ja: typeof zhCN = {
restartNow: '今すぐ再起動',
progress: '{{progress}}% · {{downloaded}} / {{total}}',
progressUnknown: 'ダウンロード済み {{downloaded}}',
installError: {
title: '更新に失敗しました',
desc: '自動更新を完了できませんでした:{{error}}。ダウンロードページから手動で最新版を入手できます。',
},
manualDownload: '手動でダウンロード',
},
},
},
Expand Down
5 changes: 5 additions & 0 deletions openless-all/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,11 @@ export const ko: typeof zhCN = {
restartNow: '지금 재시작',
progress: '{{progress}}% · {{downloaded}} / {{total}}',
progressUnknown: '다운로드됨 {{downloaded}}',
installError: {
title: '업데이트 실패',
desc: '자동 업데이트를 완료하지 못했습니다: {{error}}. 다운로드 페이지에서 최신 버전을 직접 받아 설치할 수 있습니다.',
},
manualDownload: '수동 다운로드',
},
},
},
Expand Down
5 changes: 5 additions & 0 deletions openless-all/app/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,11 @@ export const zhCN = {
restartNow: '现在重启',
progress: '{{progress}}% · {{downloaded}} / {{total}}',
progressUnknown: '已下载 {{downloaded}}',
installError: {
title: '更新失败',
desc: '自动更新没能完成:{{error}}。你可以前往下载页手动下载安装最新版本。',
},
manualDownload: '手动下载',
},
},
},
Expand Down
5 changes: 5 additions & 0 deletions openless-all/app/src/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,11 @@ export const zhTW: typeof zhCN = {
restartNow: '現在重啓',
progress: '{{progress}}% · {{downloaded}} / {{total}}',
progressUnknown: '已下載 {{downloaded}}',
installError: {
title: '更新失敗',
desc: '自動更新未能完成:{{error}}。你可以前往下載頁手動下載安裝最新版本。',
},
manualDownload: '手動下載',
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src/lib/ipc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,4 @@ export {
} from "./marketplace-cache"

// utils
export { openExternal, exportErrorLog } from "./utils"
export { openExternal, exportErrorLog, logClientError } from "./utils"
13 changes: 13 additions & 0 deletions openless-all/app/src/lib/ipc/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,16 @@ export async function exportErrorLog(
)
return target
}

/**
* 把前端关键错误(如自动更新 install 失败)转发到 Rust 文件日志(openless.log)。
* webview 的 console.error 不会落进 openless.log,单独走 IPC,便于用户「导出日志」
* 后我们拿到失败的真实原因。永不抛错——日志失败不应再影响调用方的错误处理。
*/
export async function logClientError(message: string): Promise<void> {
try {
await invokeOrMock<void>("log_client_error", { message }, () => undefined)
} catch (error) {
console.warn("[log-client-error] failed to forward error to app log", error)
}
}
1 change: 1 addition & 0 deletions openless-all/app/src/pages/settings/CheckUpdateButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export function CheckUpdateButton({ channel }: { channel: UpdateChannel }) {
progress={updater.progress}
downloaded={updater.downloaded}
contentLength={updater.contentLength}
errorMessage={updater.errorMessage}
onInstall={() => void updater.installUpdate()}
onClose={() => void updater.dismissDialog()}
/>
Expand Down
Loading