Repository navigation
fix(startup): surface startup failures instead of dying silently - #86
std-external wants to merge 4 commits into
Conversation
A failing plugin initialization aborts `Builder::run` before the first window exists; release builds run with `windows_subsystem = "windows"` and a hidden console, so the `.expect` panic left the user with no window, no dialog and nothing but one line in the log. - report a failed `run` with a native error dialog that shows the raw error, the log path and a button to open the log folder - probe the cookie cache of `tauri-plugin-http` before registering it: that plugin creates `<app cache>/.cookies` while the app starts and a machine where the path is not writable (antivirus, ACLs, redirected %LOCALAPPDATA%) fails with "拒绝访问。 (os error 5)"; repair a stale cookie file, otherwise start without the plugin instead of taking the whole launcher down - install the WebView2 Evergreen Bootstrapper directly when the runtime is missing, and keep the log path in the prompt - keep the five previous runs as celemod.1.log … celemod.5.log instead of truncating the only log, and log version/exe/cwd/os at the very start - add an `open_log_folder` command (right click on the title bar version)
|
补充一条验证:这次改动的 Windows 分支在 Linux 上编译不到,所以我单独用 |
…re set The probe swallowed the io error, so the dialog told the user the cache directory is not writable without saying which call was denied. Keep the operation and the system message (e.g. "open …\.cookies (拒绝访问。 (os error 5))") for the log and the dialog. Also correct the degraded-mode text: the Mod list, downloads, launching and the local features keep working through the Rust side, only the requests the frontend makes through tauri-plugin-http (update info, Mod site search, Everest/Loenn version lists) are skipped.
|
回答两个问题,顺便把 PR 又收紧了一点(commit 1) 降级之后不是没窗口。 主窗口和前端照常起来 —— 建窗口跟这个插件无关,前端资源也是打进 exe 的。顺序是:先弹一个原生提示框 → 点「确定」→ 正常启动(只是不注册 失效的只有前端直接发请求的那几项( 如果更想「宁可退出也别半残」,把 2) 现在能告诉用户到底哪一步被拒了。 commit |
A read-only `.cookies` (or one with a file-level deny) makes `File::options().create(true).append(true).read(true).open()` fail with ACCESS_DENIED on Windows, and that open is the *only* other way this plugin can kill the launcher: a store it merely cannot *parse* is dropped silently by tauri-plugin-http (`CookieStoreMutex::load(..).unwrap_or_else(..)`). - clear the read-only attribute, move the file to `.cookies.bad-<ms>` and probe again before giving up (verified under Wine: read-only file → `open … (Access denied. (os error 5))` → backup + fresh store) - back up a store the plugin cannot parse (e.g. BOM-only, seen in the wild) and start from an empty one, logging what was found - log which operation failed, so a `create <dir>` failure (blocked folder) and an `open <dir>\.cookies` failure (blocked file) can be told apart - tests: fresh directory, usable store, unopenable file, unparsable store, directory that cannot be used at all
|
新证据( 插件的 cookie 加载路径(
|
| 用户 A | 用户 B(本次) | |
|---|---|---|
| 失败的操作 | create <目录> 或 open <目录>\.cookies 被拒 |
open <目录>\.cookies 被拒(目录 ACL 正常) |
| 日志 | PluginInitialization("http", "拒绝访问。 (os error 5)") |
同一行(如果他的 log 里也是这行) |
| 用户侧表现 | 完全一样:双击没反应、零 UI(release 无控制台) | 同左 |
| 现在怎么区分 | 新版日志/弹框里会直接写 create … 还是 open …\.cookies |
同左 |
只读属性/文件级 ACL 为什么会出现:清理或备份工具、从压缩包/其他机器还原、杀软的"保护"改写属性等都可能;顺带一提插件自己写的是 serde_json::to_string(&cookies)(空 store 是 []),永远不会写 BOM,所以那个 BOM 文件是外部工具造出来的。
现在的行为(prepare_http_cache)
- 照插件的方式探测
create_dir_all+open(.cookies); - 打不开 → 清掉只读属性 → 改名成
.cookies.bad-<毫秒>(改不动才删)→ 再建一个空的 → 再探测;成功就照常注册http插件(不降级); - 能打开但内容不是可用的 store(BOM / 非 JSON)→ 备份一份 + 从空 store 开始,并写一条
[WARN](这样下次有日志可查); - 仍然失败才降级跳过插件并弹框,弹框里带上是
create还是open在拒绝。
测试:cargo test --lib 现在 116 passed(新增 5 个:全新目录、正常 store、打不开的文件、解析不了的 store、完全不可用的目录),另外 Windows 侧行为用上面的 Wine 用例单独跑过。
|
行号更正(上一条评论里的两处): |
An antivirus or sync client that holds `.cookies` exclusively for a moment fails the same open with a sharing violation (os error 32, not 5). Verified under Wine: a 300 ms exclusive handle is survived by the retries, a permanent one still ends in the degraded start with the reason reported.
|
更正 + 根因结论(那个 源码层能确证的部分谁写、什么时候写、怎么写(
三者最终都是空库、都不会报错;唯一区别是「文件不存在」那一步需要目录的写权限。也就是说:内容(含 0 字节)永远不会让启动失败,只有那个 open 失败会让启动失败。 Windows 错误码矩阵(Wine + 真实 Win32 语义实测,用例源码在
结论:只读属性/文件 ACL → 5;被占用 → 32。 所以「用户 A 的 5」和「第二个用户的 5」是同一条分支(open 被拒 → PR 这轮加了什么(commit
|
问题
Windows 上双击 CeleMod 完全没有反应(没有窗口、没有弹框),
%USERPROFILE%\.celemod\celemod.log里只有:原因
tauri-plugin-http 2.5.9在启动时(Builder::setup)会create_dir_all(app_cache_dir)再打开<app cache>/.cookies(插件src/lib.rs:31-46)。这个路径不可写时?直接抛 io 错误,Tauri 在tauri-2.11.5/src/plugin.rs:1009把它包成Error::PluginInitialization,Builder::run()就返回Err。backend.rs:6413(v1.2.1,master 是:6488)是.expect("error while running CeleMod")→ panic。release 构建是windows_subsystem = "windows"(main.rs:1)+隐藏控制台(initialize_windows_console),panic 只会写进日志,用户侧就是「点了没反应」。%LOCALAPPDATA%\com.celemod.desktop写不进去(杀软 / ACL / 组策略 / 重定向的 AppData)。但一个网络插件的缓存文件不该让整个启动器直接死掉。改动
native_dialog.rs(新增):不依赖 webview 的原生弹框(WindowsMessageBoxW/ macOSosascript/ Linuxzenity–kdialog)。backend.rs:run()不再.expect,失败时写日志 + 弹原生框,显示原始错误和日志路径,点「是」直接打开日志文件夹;错误里出现os error 5 / 拒绝访问时附上针对性提示(删缓存目录 / 加杀软白名单)。backend.rs:注册http插件前先探测同一个 cookie 缓存(http_plugin_cache)。能写就照常注册;遇到残留的.cookies先删掉重试一次;仍然不行就跳过该插件继续启动并弹框说明(网络功能不可用,但界面、日志、本地启动仍可用),而不是零 UI 退出。webview_runtime.rs:缺 WebView2 时直接用官方 Evergreen Bootstrapper 直链自动下载并启动安装(/silent /install),失败再回退到打开下载页;提示里带上日志路径。logging.rs:不再每次启动把唯一日志清空 —— 保留最近 5 次(celemod.1.log…celemod.5.log);并在启动最早就写入版本 / commit / exe / cwd / os,这样「缺运行库」「插件初始化失败」这类早期失败也自带版本信息。open_log_folder命令;标题栏版本号右键改为打开日志文件夹(Shift+左键仍是打开日志文件)。验证
cargo test --lib:114 passed(新增logging::tests4 个日志轮转测试、http_plugin_cache_tests3 个缓存探测/修复测试)pnpm typecheck通过;test:catalog-download(3)、test:crash-trace(3)、test:deep-link(3)、test:update-notes(5) 全绿pnpm test:i18n在 master 上本来就是坏的(脚本指向不存在的scripts/deepseek-translator.test.cjs,仓库里只有deepseek-translator.cjs),与本次改动无关。备注
if http_available那段去掉、只保留可见错误弹窗即可。http从原来的dialog之前移到了 deep-link 之前,两者没有依赖关系。