Skip to content

修复若干问题,主要是Linux适配引起的回归问题 - #70

Open
Starrah wants to merge 7 commits into
MuNET-OSS:mainfrom
Starrah:fix-import-track
Open

修复若干问题,主要是Linux适配引起的回归问题#70
Starrah wants to merge 7 commits into
MuNET-OSS:mainfrom
Starrah:fix-import-track

Conversation

@Starrah

@Starrah Starrah commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

涉及项目构建、前端逻辑、MP3兼容模式等,具体内容详见commit message。

Summary by Sourcery

改进跨平台行为,特别是针对 Linux/导出模式、音频解码和宿主检测,同时优化谱面导入体验和本地化。

新特性:

  • 通过应用版本 API 暴露应用平台和导出模式标志,以供前端使用。
  • 在生成的前端客户端 API 中新增一个强类型的商店购买状态枚举。

错误修复:

  • 确保在提取纹理后正确卸载 asset bundle 文件,以避免资源泄漏或特定平台上的故障。
  • 修复 ImportBrowse 端点在导出模式下返回泛型 500 错误的问题,改为显式返回 HTTP 403,而不是依赖框架驱动的 Forbid()。
  • 将 MP3 兼容模式限制在 Windows 上,并在非 Windows 平台隐藏“忽略无间隙播放”选项,以防止在 Linux 上出现不受支持的行为。
  • 改进 Photino 宿主检测,使得通过普通浏览器访问导出模式时不再被误判为 Photino。
  • 简化谱面导入错误中的 AbortError 检测,使其在不同环境中表现一致。

增强:

  • 明确批量 PV 转换和“音乐没有有效谱面”消息的本地化文案。
  • 通过在输入已为 WAV 时绕过 ffmpeg,以及将大多数格式统一到单一基于 ffmpeg 的路径,优化音频转换流程。
  • 扩展应用版本 DTO 和前端类型以携带平台和导出标志,从而支持平台感知的 UI 行为。
Original summary in English

Summary by Sourcery

Improve cross-platform behavior, especially around Linux/export modes, audio decoding, and host detection, while tightening chart import UX and localization.

New Features:

  • Expose app platform and export mode flags via the app version API for frontend use.
  • Add a typed store purchase status enum to the generated frontend client API.

Bug Fixes:

  • Ensure asset bundle files are properly unloaded after texture extraction to avoid resource leaks or platform-specific failures.
  • Fix ImportBrowse endpoints returning generic 500 errors in export mode by explicitly responding with HTTP 403 instead of framework-driven Forbid().
  • Restrict MP3 compatibility mode to Windows and hide the ignore-gapless option on non-Windows platforms to prevent unsupported behavior on Linux.
  • Improve Photino host detection so export mode access via a normal browser is no longer misclassified as Photino.
  • Simplify AbortError detection in chart import errors to work uniformly across environments.

Enhancements:

  • Clarify localization strings for batch PV conversion and the 'music has no valid charts' message.
  • Optimize audio conversion by bypassing ffmpeg when input is already WAV and by standardizing most formats on a single ffmpeg-based path.
  • Extend the app version DTO and frontend types to carry platform and export flags, enabling platform-aware UI behavior.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@sourcery-ai

sourcery-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

审阅者指南

该 PR 通过调整音频解码行为、平台检测、本地化字符串、前端 API/类型以及导入/导出行为,并从解决方案中清理未使用的项目,修复了主要由 Linux 适配引起的回归问题。

AppVersionResult 的 platform/export 与 ignoreGapless 选项时序图

sequenceDiagram
    actor User
    participant Frontend
    participant AppVersionController
    participant StaticSettings

    User->>Frontend: load ImportChartButton
    Frontend->>AppVersionController: GetAppVersion()
    AppVersionController->>StaticSettings: read gameVersion, Config.Export, CurrentLocale
    AppVersionController-->>Frontend: AppVersionResult(version, gameVersion, license, hardwareAcceleration, h264Encoder, locale, platform, export)
    Frontend->>Frontend: store version in refs.version
    Frontend->>Frontend: render ignoreGapless only when version.platform == Windows
Loading

文件级变更

变更 详情 文件
确保在 PNG 提取后正确卸载纹理 bundle 文件,以避免资源泄漏并提升健壮性。
  • 将 bundle 加载与资源遍历包装在 try/finally 中
  • 使用可空变量跟踪 BundleFileInstance
  • 在 finally 中当 bundle 不为 null 时调用 AssetsManager.UnloadBundleFile
  • 在未找到任何 Texture2D 时返回 null
MaiChartManager/Utils/ImageConvert.cs
从后端向前端暴露平台与导出模式标志,并细化 Linux 构建下的 license 状态建模。
  • 在 Windows 与非 Windows 上扩展 AppVersionResult DTO,包含 platform 字符串与 export 布尔值
  • 使用 OperatingSystem.IsWindows 将 platform 设置为 "Windows" 或 "Linux"
  • 通过 AppVersionController 传递 StaticSettings.Config.Export
  • 为非 Windows 流程定义 LicenseStatus 枚举
  • 更新前端 AppVersionResult 接口以包含 platform 与 export 字段
MaiChartManager/Controllers/App/AppVersionController.cs
MaiChartManager/Front/src/client/apiGen.ts
优化音频转换逻辑,将 MP3 兼容模式限制为仅在 Windows 上启用,并避免对 WAV 输入不必要地使用 ffmpeg。
  • 仅在运行于 Windows 时,通过 MediaFoundation 处理带兼容标志的 .mp3
  • 保持其他格式以及非兼容 MP3 继续通过基于 ffmpeg 的 WAV 转换
  • 引入 SupportsMp3CompatibilityMode 帮助方法,检查 OperatingSystem.IsWindows
  • 在 ConvertToWavViaFfmpeg 中对 WAV 输入直接复制流以短路处理
  • 对其他格式保留基于临时文件的 ffmpeg 流程
MaiChartManager/Utils/Audio.cs
改进前端平台检测与特性开关,特别是 Photino 宿主检测以及 Windows 专用 MP3 兼容选项的可见性。
  • 修改 isPhotino 检测逻辑,要求同时存在 window.external.sendMessage,以避免将导出模式下的浏览器误判为 Photino
  • 向错误显示的导入谱面组件注入版本 store
  • 仅在 version.value.platform 为 Windows 时显示 ignoreGapless 选项 UI
MaiChartManager/Front/src/client/api.ts
MaiChartManager/Front/src/views/Charts/ImportCreateChartButton/ImportChartButton/ErrorDisplayIdInput.tsx
简化导入错误处理中的中止错误检测,以在不同环境中统一行为。
  • 用通用的 name === "AbortError" 判断替换对 DOMException/Error 的 instanceof 检查
  • 放宽 errorValue 的类型为 any 以避免类型问题
MaiChartManager/Front/src/views/Charts/ImportCreateChartButton/ImportChartButton/importErrors.ts
调整导入浏览端点,在导出/远程模式下返回明确的 403 状态码,以避免框架与认证相关的异常。
  • 在文件夹选择与列表接口中,将 Forbid() 响应改为 StatusCode(StatusCodes.Status403Forbidden)
  • 保持资源路径校验与日志记录
MaiChartManager/Controllers/AssetDir/ImportBrowseController.cs
更新本地化资源和生成的 designer,新增批量 PV 转换消息并优化与谱面相关的文案。
  • 在 resx 文件中添加 BatchConvertPvFolderNotFound、BatchConvertPvNeedLicense 和 BatchConvertPvNoFiles 字符串
  • 重新排序 designer 属性以匹配资源变更
  • 将 MusicNoCharts 的摘要从 “Music has no charts” 改为 “Music has no valid charts”
MaiChartManager/Locale.Designer.cs
MaiChartManager/Locale.resx
MaiChartManager/Locale.zh-Hans.resx
MaiChartManager/Locale.zh-Hant.resx
通过移除未使用项目来清理解决方案结构。
  • 从仓库/解决方案中移除 MaiLib 项目
  • 从仓库/解决方案中移除 SimaiSharp 项目
  • 更新 Sitreamai.slnx 以反映这些项目的移除
Sitreamai.slnx
MaiLib
SimaiSharp
通过引入状态码枚举,使购买结果类型与后端语义对齐。
  • 定义 StorePurchaseStatus 枚举,表示购买结果状态
  • 将 RequestPurchaseResult.status 从 int 改为 StorePurchaseStatus
MaiChartManager/Front/src/client/apiGen.ts

提示与命令

与 Sourcery 交互

  • 触发新的审阅: 在 Pull Request 上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 根据审阅评论生成 GitHub issue: 在审阅评论下回复,请求 Sourcery 从该评论创建 issue。你也可以回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题的任意位置写入 @sourcery-ai,即可随时生成标题。你也可以在 Pull Request 中评论 @sourcery-ai title 以随时(重新)生成标题。
  • 生成 Pull Request 摘要: 在 Pull Request 正文任意位置写入 @sourcery-ai summary,即可在指定位置生成 PR 摘要。你也可以在 Pull Request 中评论 @sourcery-ai summary 以随时(重新)生成摘要。
  • 生成审阅者指南: 在 Pull Request 中评论 @sourcery-ai guide,即可随时(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve,即可标记解决所有 Sourcery 评论。适用于你已经处理完所有评论且不想再看到它们的情况。
  • 撤销所有 Sourcery 审阅: 在 Pull Request 中评论 @sourcery-ai dismiss,即可撤销所有现有的 Sourcery 审阅。特别适合你希望从头开始新一轮审阅时使用——别忘了再评论 @sourcery-ai review 以触发新的审阅!

自定义你的体验

访问你的控制面板 以:

  • 启用或禁用审阅特性,例如 Sourcery 生成的 Pull Request 摘要、审阅者指南等。
  • 更改审阅语言。
  • 添加、移除或编辑自定义审阅指令。
  • 调整其他审阅设置。

获取帮助

Original review guide in English

Reviewer's Guide

PR fixes regressions mainly caused by Linux adaptation by adjusting audio decoding behavior, platform detection, localized strings, frontend API/types, and import/export behavior, plus cleaning unused projects from the solution.

Sequence diagram for AppVersionResult platform/export and ignoreGapless option

sequenceDiagram
    actor User
    participant Frontend
    participant AppVersionController
    participant StaticSettings

    User->>Frontend: load ImportChartButton
    Frontend->>AppVersionController: GetAppVersion()
    AppVersionController->>StaticSettings: read gameVersion, Config.Export, CurrentLocale
    AppVersionController-->>Frontend: AppVersionResult(version, gameVersion, license, hardwareAcceleration, h264Encoder, locale, platform, export)
    Frontend->>Frontend: store version in refs.version
    Frontend->>Frontend: render ignoreGapless only when version.platform == Windows
Loading

File-Level Changes

Change Details Files
Ensure texture bundle files are properly unloaded after PNG extraction to avoid resource leaks and improve robustness.
  • Wrap bundle loading and asset iteration in try/finally
  • Track BundleFileInstance in a nullable variable
  • Call AssetsManager.UnloadBundleFile in finally when bundle is not null
  • Return null when no Texture2D is found
MaiChartManager/Utils/ImageConvert.cs
Expose platform and export-mode flags from the backend to the frontend and refine license status modeling for Linux builds.
  • Extend AppVersionResult DTO on both Windows and non-Windows to include platform string and export boolean
  • Use OperatingSystem.IsWindows to set platform to "Windows" or "Linux"
  • Pass StaticSettings.Config.Export through AppVersionController
  • Define LicenseStatus enum for non-Windows flows
  • Update frontend AppVersionResult interface to include platform and export fields
MaiChartManager/Controllers/App/AppVersionController.cs
MaiChartManager/Front/src/client/apiGen.ts
Refine audio conversion logic, adding MP3 compatibility mode gating to Windows only and avoiding unnecessary ffmpeg use for WAV inputs.
  • Route .mp3 with compatibility flag through MediaFoundation only when running on Windows
  • Keep other formats and non-compat MP3 going via ffmpeg-based WAV conversion
  • Introduce SupportsMp3CompatibilityMode helper that checks OperatingSystem.IsWindows
  • Short-circuit WAV inputs in ConvertToWavViaFfmpeg by copying the stream directly
  • Retain temp-file-based ffmpeg pipeline for other formats
MaiChartManager/Utils/Audio.cs
Improve frontend platform detection and feature gating, particularly Photino host detection and Windows-only MP3 compatibility option visibility.
  • Change isPhotino detection to also require window.external.sendMessage to avoid misclassifying export-mode browsers
  • Inject version store into error-display import chart component
  • Only show ignoreGapless option UI when platform is Windows based on version.value.platform
MaiChartManager/Front/src/client/api.ts
MaiChartManager/Front/src/views/Charts/ImportCreateChartButton/ImportChartButton/ErrorDisplayIdInput.tsx
Simplify abort-error detection in import errors handling to work uniformly across environments.
  • Replace instanceof DOMException/Error checks with a generic name === "AbortError" guard
  • Relax type of errorValue to any to avoid type issues
MaiChartManager/Front/src/views/Charts/ImportCreateChartButton/ImportChartButton/importErrors.ts
Adjust import browsing endpoints to return explicit 403 status codes in export/remote mode to avoid framework authentication-related exceptions.
  • Change Forbid() responses to StatusCode(StatusCodes.Status403Forbidden) in folder picking and listing
  • Maintain asset path validation and logging
MaiChartManager/Controllers/AssetDir/ImportBrowseController.cs
Update localization resources and generated designer to add batch PV conversion messages and refine chart-related wording.
  • Add BatchConvertPvFolderNotFound, BatchConvertPvNeedLicense, and BatchConvertPvNoFiles strings to resx files
  • Reorder designer properties to match resource changes
  • Change MusicNoCharts summary text from "Music has no charts" to "Music has no valid charts"
MaiChartManager/Locale.Designer.cs
MaiChartManager/Locale.resx
MaiChartManager/Locale.zh-Hans.resx
MaiChartManager/Locale.zh-Hant.resx
Clean up solution structure by removing unused projects.
  • Remove MaiLib project from the repository/solution
  • Remove SimaiSharp project from the repository/solution
  • Update Sitreamai.slnx to reflect the removal of these projects
Sitreamai.slnx
MaiLib
SimaiSharp
Align purchase result typing with backend semantics by introducing an enum for status codes.
  • Define StorePurchaseStatus enum with purchase outcome values
  • Change RequestPurchaseResult.status from int to StorePurchaseStatus
MaiChartManager/Front/src/client/apiGen.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我在这里给出一些高层次的反馈:

  • AppVersionController 中,Platform 字段是通过 OperatingSystem.IsWindows() 计算出来的,而这个调用本身已经处于特定平台的编译分支中;你可以通过在 WINDOWS 分支中直接返回常量 "Windows",在非 Windows 分支中返回 "Linux" 来简化逻辑并让意图更加清晰。
  • 新增的 importErrors.ts 中的 isAbortError 辅助函数现在接受 any 类型,并且会把任何 name === 'AbortError' 的对象当作中止错误;建议收窄可接受的类型(例如 DOMException | Error),或者增加更严格的结构检查,以避免把任意对象误分类为中止错误。
给 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- In `AppVersionController` the `Platform` field is computed via `OperatingSystem.IsWindows()` inside already platform-specific compilation branches; you could simplify and make intent clearer by returning constant "Windows" in the WINDOWS branch and "Linux" in the non-Windows branch.
- The new `isAbortError` helper in `importErrors.ts` now accepts `any` and treats any object with `name === 'AbortError'` as an abort; consider narrowing the accepted types (e.g. DOMException | Error) or adding a stricter shape check to avoid misclassifying arbitrary objects.

Sourcery 对开源项目免费——如果你觉得我们的代码审查有帮助,请考虑分享给更多人 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进之后的代码审查。
Original comment in English

Hey - I've left some high level feedback:

  • In AppVersionController the Platform field is computed via OperatingSystem.IsWindows() inside already platform-specific compilation branches; you could simplify and make intent clearer by returning constant "Windows" in the WINDOWS branch and "Linux" in the non-Windows branch.
  • The new isAbortError helper in importErrors.ts now accepts any and treats any object with name === 'AbortError' as an abort; consider narrowing the accepted types (e.g. DOMException | Error) or adding a stricter shape check to avoid misclassifying arbitrary objects.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `AppVersionController` the `Platform` field is computed via `OperatingSystem.IsWindows()` inside already platform-specific compilation branches; you could simplify and make intent clearer by returning constant "Windows" in the WINDOWS branch and "Linux" in the non-Windows branch.
- The new `isAbortError` helper in `importErrors.ts` now accepts `any` and treats any object with `name === 'AbortError'` as an abort; consider narrowing the accepted types (e.g. DOMException | Error) or adding a stricter shape check to avoid misclassifying arbitrary objects.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 15 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread MaiChartManager/Utils/Audio.cs Outdated
否则,压缩 WAV(例如 ADPCM)现在会在导入时失败

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Auto-approved: 主要变更修复资源泄露、平台兼容性限制、导出模式错误处理、宿主检测改进以及项目构建调整,均为明确的 bug 修复和清理,没有需要人工审批的行为权衡。

Re-trigger cubic

{
// 仅本地桌面场景,export / 远程模式下禁止
if (StaticSettings.Config.Export) return Forbid();
if (StaticSettings.Config.Export) return StatusCode(StatusCodes.Status403Forbidden);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

诶,那这样的话,export+应用内打开是不是会炸
(至少 Windows 是有 export+应用内打开的操作的,我不记得 Linux 做没做了)

@Starrah Starrah Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

首先,原本的代码return Forbid();26530b1 中引入的,并不是我写的;我推测这么做的目的可能是,远程情况下浏览器和后端不运行在一台机器上,很多情况下后端是游戏专机之类的、可能连完整的鼠标键盘都没,因此从后端的机器调用本机的文件选择API、弹出文件选择窗口,前端那边也是看不到,没有意义的。但实际上,在(修改后的)正常的逻辑之下,理论上Export模式应该一定调用的是浏览器侧的文件选择API(因为isPhotino必定工作在非export模式下?见下面讨论),所以这个代码其实只是个Guard;

// 通用选目录:
// - Photino(WebKitGTK):一律走后端原生对话框。WebKitGTK 即使暴露了 window.showDirectoryPicker,
// 其实现也有问题(调用后访问 handle 会抛 "The string did not match the expected pattern"),
// 所以**不能**用 typeof 检测来决定,必须在 showDirectoryPicker 之前优先判 isPhotino。
// - 其它有真实 File System Access API 的环境(WebView2 / 远程 Chrome):用真实 handle,行为不变。
// - 其余本地宿主但无可用 picker:兜底走后端。
// - 都不满足(远程浏览器且无 File System Access API):不支持,按取消处理。
export async function pickDirectory(
options?: { id?: string; startIn?: string },
): Promise<ImportDirectory> {
// Photino/WebKitGTK:优先后端,绕开有问题的 webkit showDirectoryPicker
if (isPhotino) {
return pickViaBackend();
}
// 真实 File System Access API(WebView2 / Chromium / 远程 Chrome)
if (typeof window.showDirectoryPicker === 'function') {
// 真实 FileSystemDirectoryHandle 在结构上满足 ImportDirectory
return window.showDirectoryPicker(options as any) as unknown as Promise<ImportDirectory>;
}
// 其它本地桌面宿主(无 showDirectoryPicker):后端原生选目录
if (isLocalHost) {
return pickViaBackend();
}
// 远程浏览器且无 File System Access API:不支持,按取消处理
abort('当前环境不支持选择目录');
}

其次,我这里的修改只是把return Forbid();改成了return StatusCode(StatusCodes.Status403Forbidden);,因为测试发现,return Forbid();由于一些配置上的原因,无法返回403,而是抛异常导致500。(AI分析如下图)
{4DFF3F16-2036-4121-9D0C-B297CC15A4A9}

最后,直接回答您的”export+应用内打开是不是会炸“的问题。分两个方面:

  • Windows而言不会炸(我已实际测试)。这是因为即便是export+应用内打开,应用内打开调的也是WebView2,而Windows上的WebView2的实现是完整的,带有正常的浏览器侧的文件选择API。因此文件选择窗口最终实际上还是由前端WebView打开的,根本不会调用到您引用的这个后端函数。
  • Linux上。其实最简单的办法当然也是前端打开,但是根据pickDirectory.ts中的注释,之所以不这么做是因为WebKitGTK 即使暴露了 window.showDirectoryPicker,其实现也有问题(调用后访问 handle 会抛 "The string did not match the expected pattern"),所以只好退而求其次用后端文件选择作为workaround。所以,问题的关键就是isPhotino是不是保证一定非export模式。 如果是的话则没问题;不然的话,则这里确实是个问题(但也是从 26530b1 就引入了的)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好的 我觉得我应该检查一下,就不让 Linux 在 export 模式下再在应用内打开好了

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好呀,我觉得可以,因为「选择」这个行为本身也一定是在本机上完成的,因为弹窗只能在本机上弹出来
不过我是希望,如果不是 localhost 的访问,或者不是 photino 而是本机 chromium 之类的,还是用像原先一样的浏览器端直接选择文件夹,而不需要走这个绕一圈(

@Starrah Starrah Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好的 我觉得我应该检查一下,就不让 Linux 在 export 模式下再在应用内打开好了

讲一点我的观察,供您参考:

  • 在完全不考虑安全性的情况下:这个“export模式下返回403”的逻辑其实没有意义、不会命中,干脆可以去掉。因为在正常的程序逻辑下,后端文件选择函数PickImportFolderAPI一定是前端通过pickViaBackend触发的。阅读相关代码容易知道,它一定是在localhost访问的情况下才会触发的(isPhotino内也隐含了isLocalhost的判断的),所以正常情况下并不可能出现“用户在其他机器上远程访问,却在后端机器上弹出了个对话框”的情况。
    // 通用选目录:
    // - Photino(WebKitGTK):一律走后端原生对话框。WebKitGTK 即使暴露了 window.showDirectoryPicker,
    // 其实现也有问题(调用后访问 handle 会抛 "The string did not match the expected pattern"),
    // 所以**不能**用 typeof 检测来决定,必须在 showDirectoryPicker 之前优先判 isPhotino。
    // - 其它有真实 File System Access API 的环境(WebView2 / 远程 Chrome):用真实 handle,行为不变。
    // - 其余本地宿主但无可用 picker:兜底走后端。
    // - 都不满足(远程浏览器且无 File System Access API):不支持,按取消处理。
    export async function pickDirectory(
    options?: { id?: string; startIn?: string },
    ): Promise<ImportDirectory> {
    // Photino/WebKitGTK:优先后端,绕开有问题的 webkit showDirectoryPicker
    if (isPhotino) {
    return pickViaBackend();
    }
    // 真实 File System Access API(WebView2 / Chromium / 远程 Chrome)
    if (typeof window.showDirectoryPicker === 'function') {
    // 真实 FileSystemDirectoryHandle 在结构上满足 ImportDirectory
    return window.showDirectoryPicker(options as any) as unknown as Promise<ImportDirectory>;
    }
    // 其它本地桌面宿主(无 showDirectoryPicker):后端原生选目录
    if (isLocalHost) {
    return pickViaBackend();
    }
    // 远程浏览器且无 File System Access API:不支持,按取消处理
    abort('当前环境不支持选择目录');
    }
  • 所以AI原本的顾虑其实是安全性问题。也就是恶意用户,并不通过我们的前端,而是直接强行请求API的情况。
    // maidata 导入用的后端目录浏览接口。
    // 背景:WebKitGTK(Linux/Photino)既没有 showDirectoryPicker,<input webkitdirectory> 也只能选单文件,
    // 所以前端没法在浏览器侧拿到目录内容。改为:后端弹原生选文件夹对话框,再通过下面 3 个接口
    // 把所选目录的内容提供给前端的 ImportDirectory 适配器(见 Front/src/utils/httpImportDirectory.ts)。
    //
    // 安全说明:这些接口可以读取任意本地路径,仅限「本地桌面 + 仅 loopback」场景使用。
    // 切勿在 export / 远程模式下启用——否则等于把整个文件系统暴露给局域网。
    // 因此每个接口都先校验 !StaticSettings.Config.Export。
    [ApiController]
    [Route("MaiChartManagerServlet/[action]Api")]
    public class ImportBrowseController(IDesktopDialogService dialogService, ILogger<ImportBrowseController> logger) : ControllerBase
    • 后端提供的文件操作API中除了有PickImportFolder这种只是弹对话框、本身无害的,但也有ListImportDirReadImportFile这种可以实现任意文件读取的。 确实可能有明显的安全风险。

综上以上大概是我对情况的分析和总结,具体怎么改看您

@Starrah Starrah Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好呀,我觉得可以,因为「选择」这个行为本身也一定是在本机上完成的,因为弹窗只能在本机上弹出来 不过我是希望,如果不是 localhost 的访问,或者不是 photino 而是本机 chromium 之类的,还是用像原先一样的浏览器端直接选择文件夹,而不需要走这个绕一圈(

呜呜我一开始分析和判断的有问题,我删了重发了,您以我后面发的为准

就,弹窗本身确实没有什么危害,但麻烦的点在于这套文件操作API里还提供了ListImportDirReadImportFile这种可以实现任意文件读取的大雷。

@Starrah Starrah Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果不是 localhost 的访问,或者不是 photino 而是本机 chromium 之类的,还是用像原先一样的浏览器端直接选择文件夹,而不需要走这个绕一圈(

对啊,现在就已经是这样的了,只有isPhotinopickViaBackend,否则还是前端文件选择

export async function pickDirectory(
options?: { id?: string; startIn?: string },
): Promise<ImportDirectory> {
// Photino/WebKitGTK:优先后端,绕开有问题的 webkit showDirectoryPicker
if (isPhotino) {
return pickViaBackend();
}
// 真实 File System Access API(WebView2 / Chromium / 远程 Chrome)
if (typeof window.showDirectoryPicker === 'function') {
// 真实 FileSystemDirectoryHandle 在结构上满足 ImportDirectory
return window.showDirectoryPicker(options as any) as unknown as Promise<ImportDirectory>;
}
// 其它本地桌面宿主(无 showDirectoryPicker):后端原生选目录
if (isLocalHost) {
return pickViaBackend();
}
// 远程浏览器且无 File System Access API:不支持,按取消处理
abort('当前环境不支持选择目录');
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

或者把”打开界面“的按钮改成拉起系统浏览器打开网页,而不是photino 我其实推荐这个

说真的,我最早的 Linux 想法就是纯后端,然后拉起 {google-chrome,chromium,microsoft-edge} --app=http://localhost。其实这样似乎是体验更好的,因为现在 webkit-git 甚至还有一些显示 bug,而 --app 的话,甚至不会有浏览器自带的 chrome(指地址栏、工具栏这些,不是谷歌浏览器)就和 Windows 上体验差不多。

但是我觉得可能不是所有人电脑上都有 chromium 系浏览器,而且后来调查发现了 Photino 这个东西,但是我也不知道 webkit-gtk 有这么多不兼容的东西,我就拿这个做了,后来才发现一堆不兼容的坑然后开始填

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

或者把”打开界面“的按钮改成拉起系统浏览器打开网页,而不是photino 我其实推荐这个

说真的,我最早的 Linux 想法就是纯后端,然后拉起 {google-chrome,chromium,microsoft-edge} --app=http://localhost。其实这样似乎是体验更好的,因为现在 webkit-git 甚至还有一些显示 bug,而 --app 的话,甚至不会有浏览器自带的 chrome(指地址栏、工具栏这些,不是谷歌浏览器)就和 Windows 上体验差不多。

但是我觉得可能不是所有人电脑上都有 chromium 系浏览器,而且后来调查发现了 Photino 这个东西,但是我也不知道 webkit-gtk 有这么多不兼容的东西,我就拿这个做了,后来才发现一堆不兼容的坑然后开始填

所以变动性比较小的改动是不是,本地模式还是photino不变,远程模式的话直接xdg-open一个http://,这样应该会调用系统默认浏览器。尽管不是所有人电脑上都有 chromium 系浏览器,但应该所有人电脑上都至少有个浏览器吧,而且大概率会是个支持window.showDirectoryPicker的浏览器

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

而且大概率会是个支持window.showDirectoryPicker的浏览器

似乎,支持这个的浏览器也就是 chromium 系的浏览器了

不过不支持的他们也有办法自行解决吧,毕竟用 Linux 了应该默认是有能力的了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants