Conversation
Also fix GenClient to use tsx instead of ts-node (ts-node not installed, tsx is available in devDependencies).
…puted to shouldShowUpdateController.ts for detecting\nMuMod-only mode. Update shouldShowUpdate to compare MuMod cache version\nagainst latest when in MuMod mode. Add muModChannel, isBothModsPresent\ncomputed refs and updateMuModChannel async function to refs.ts.
…latestVersion respects MuMod channel
审阅者指南集成 MuMod 作为 AquaMai 的备选方案,新增用于管理 MuMod 安装与缓存的后端服务和 API,通过共享的 ModConfigService 统一配置加载逻辑,并更新前端 Mod Manager UI 与设置页,以支持 MuMod 渠道、冲突处理以及异步重新扫描。 从 Mod Manager 下拉菜单安装 MuMod 的时序图sequenceDiagram
actor User
participant ModManagerUI as ModInstallDropdown
participant ApiClient as Api_Client
participant InstallCtrl as InstallationController
participant MuModSvc as MuModService
participant FS as FileSystem
participant VersionApi as Version_API
participant SigVerifier as AquaMaiSignatureV2
User->>ModManagerUI: Click MuMod option in DropMenu
ModManagerUI->>ModManagerUI: doInstall(mumod)
ModManagerUI->>ApiClient: InstallMuMod()
ApiClient->>InstallCtrl: POST InstallMuModApi
InstallCtrl->>FS: CopyFile(MuModDllBuiltinPath, MuModDllInstalledPath)
alt AquaMai.dll exists
InstallCtrl->>FS: Delete(AquaMaiDllInstalledPath)
end
alt MuMod.toml not exists
InstallCtrl->>FS: WriteAllText(MuModConfigPath, default config)
end
InstallCtrl->>MuModSvc: EnsureCache()
MuModSvc->>MuModSvc: GetResolvedCachePath()
MuModSvc->>FS: File.Exists(cachePath)
alt Cache up to date
MuModSvc-->>InstallCtrl: EnsureCacheResult(Success=true, Version, Error=null)
else Cache missing or outdated
MuModSvc->>MuModSvc: ResolveApiType(ReadConfig().Channel)
MuModSvc->>VersionApi: FetchVersionInfosAsync(CosVersionApiUrl)
MuModSvc->>VersionApi: FetchVersionInfosAsync(CfVersionApiUrl)
VersionApi-->>MuModSvc: VersionInfoModel[]
MuModSvc->>MuModSvc: Select VersionInfoModel by Type
MuModSvc->>VersionApi: DownloadFromUrlsAsync(urls)
VersionApi-->>MuModSvc: byte[] cacheData
MuModSvc->>SigVerifier: VerifySignature(cacheData)
SigVerifier-->>MuModSvc: VerifyStatus
MuModSvc->>FS: WriteAllBytesAsync(tempCachePath, cacheData)
MuModSvc->>FS: Move(tempCachePath, cachePath, overwrite=true)
MuModSvc-->>InstallCtrl: EnsureCacheResult(Success, Version, Error)
end
InstallCtrl-->>ApiClient: 200 OK
ApiClient-->>ModManagerUI: Response
ModManagerUI->>ApiClient: GetGameModInfo()
ApiClient-->>ModManagerUI: GameModInfo(muModInstalled=true,...)
ModManagerUI->>ApiClient: GetAquaMaiConfig(forceDefault=false, skipSignatureCheck=false)
ApiClient->>ConfigCtrl: GET GetAquaMaiConfigApi
participant ConfigCtrl as ConfigurationController
ConfigCtrl->>ModConfigSvc: GetAquaMaiDllPath()
participant ModConfigSvc as ModConfigService
ModConfigSvc->>MuModSvc: IsMuModInstalled()
MuModSvc-->>ModConfigSvc: bool
ModConfigSvc->>FS: File.Exists(AquaMaiDllInstalledPath)
alt Only MuMod installed
ModConfigSvc->>MuModSvc: GetResolvedCachePath()
MuModSvc-->>ModConfigSvc: cachePath
ModConfigSvc-->>ConfigCtrl: dllPath=cachePath
else AquaMai installed
ModConfigSvc-->>ConfigCtrl: dllPath=AquaMaiDllInstalledPath
end
ConfigCtrl->>FS: FileStream(dllPath)
ConfigCtrl->>ModConfigSvc: GetCurrentAquaMaiConfig(forceDefault, shouldSkipSignatureCheck)
ModConfigSvc->>FS: ReadAllBytes(dllPath)
ModConfigSvc->>SigVerifier: VerifySignature(binary)
SigVerifier-->>ModConfigSvc: VerifyStatus.Valid
ModConfigSvc->>ModConfigSvc: CheckConfigApiVersion(configInterface)
ModConfigSvc->>FS: File.Exists(AquaMaiConfigPath)
alt Config exists and !forceDefault
ModConfigSvc->>FS: ReadAllText(AquaMaiConfigPath)
ModConfigSvc->>ModConfigSvc: Migrate and Parse config
ModConfigSvc->>StaticSettings: UpdateAssetPathsFromAquaMaiConfig(config)
participant StaticSettings
end
ModConfigSvc-->>ConfigCtrl: IConfig
ConfigCtrl-->>ApiClient: AquaMaiConfigDto
ApiClient-->>ModManagerUI: AquaMaiConfigDto
ModManagerUI->>ModManagerUI: updateModInfo(), updateAquaMaiConfig()
ModManagerUI-->>User: UI shows MuMod installed, channel and cache status
MuMod 与统一 Mod 配置服务的类图classDiagram
class MuModService {
-logger ILogger~MuModService~
+MuModService(logger ILogger~MuModService~)
+MuModConfigModel ReadConfig()
+void WriteChannel(channel string)
+string GetResolvedCachePath()
+Task~EnsureCacheResult~ EnsureCache(ct CancellationToken)
+string? GetCacheInfo()
+bool IsMuModInstalled()
+string? GetMuModVersion()
-string ResolveApiType(channel string)
-string? ReadProductVersion(path string)
-string? NormalizeVersion(version string)
-Task~(VersionInfoModel,VersionSource)~ ResolveVersionInfoAsync(apiType string, ct CancellationToken)
-IEnumerable~string~ BuildDownloadUrls(info VersionInfoModel, source VersionSource)
-Task~VersionInfoModel[]~ FetchVersionInfosAsync(url string, ct CancellationToken)
-Task~byte[]~ DownloadFromUrlsAsync(urls IReadOnlyList~string~, ct CancellationToken)
}
class MuModConfigModel {
+string Channel
+string CachePath
}
class ModConfigService {
-MuModService _muModService
+ModConfigService(muModService MuModService)
+void CheckConfigApiVersion(configInterface HeadlessConfigInterface)
+Task~string~ GetAquaMaiDllPath(ct CancellationToken)
+Task~IConfig~ GetCurrentAquaMaiConfig(forceDefault bool, skipSignatureCheck bool, ct CancellationToken)
<<exception>> UnsupportedConfigApiVersionException
<<exception>> ConfigCorruptedException
<<exception>> AquaMaiNotInstalledException
<<exception>> AquaMaiSignatureVerificationFailedException
}
class InstallationController {
-StaticSettings settings
-ILogger~InstallationController~ logger
-MuModService muModService
+InstallationController(settings StaticSettings, logger ILogger~InstallationController~, muModService MuModService)
+GameModInfo GetGameModInfo()
+void InstallMelonLoader()
+void InstallAquaMai()
+Task InstallAquaMaiOnline(req InstallAquaMaiOnlineDto)
+Task InstallMuMod()
+void DeleteAquaMai()
+void DeleteMuMod()
+void KillGameProcess()
}
class ConfigurationController {
-StaticSettings settings
-ILogger~ConfigurationController~ logger
-ModConfigService modConfigService
+ConfigurationController(settings StaticSettings, logger ILogger~ConfigurationController~, modConfigService ModConfigService)
+Task~AquaMaiConfigDto.ConfigDto~ GetAquaMaiConfig(forceDefault bool, skipSignatureCheck bool)
+Task SetAquaMaiConfig(config AquaMaiConfigDto.ConfigSaveDto)
}
class StaticSettings {
-ILogger~StaticSettings~ _logger
-ModConfigService _modConfigService
+StaticSettings(logger ILogger~StaticSettings~, modConfigService ModConfigService)
+Task InitializeGameData()
+Task RescanAll()
+List~MusicXmlWithABJacket~ GetMusicList()
+string GetFreeAssetDir()
+static void UpdateAssetPathsFromAquaMaiConfig(config IConfig)
}
class MuModController {
-MuModService muModService
+MuModController(muModService MuModService)
+MuModConfigDto GetMuModConfig()
+Task~EnsureCacheResultDto~ SetMuModChannelAndEnsureCache(req SetChannelDto)
+Task~EnsureCacheResultDto~ EnsureMuModCache()
}
class ModPaths {
+string AquaMaiConfigPath
+string AquaMaiConfigBackupDirPath
+string AquaMaiDllInstalledPath
+string AquaMaiDllBuiltinPath
+string MuModDllInstalledPath
+string MuModDllBuiltinPath
+string MuModConfigPath
+string MuModDefaultCachePath
}
class GameModInfo {
+bool MelonLoaderInstalled
+bool AquaMaiInstalled
+string AquaMaiVersion
+string BundledAquaMaiVersion
+bool IsVersionTooLow
+bool IsHidConflictExist
+VerifyResult Signature
+bool IsAdxHidIoModAbsent
+bool IsMmlLegacyLibsInstalled
+bool MuModInstalled
+string MuModVersion
+string MuModChannel
+string MuModCacheVersion
+bool IsBothModsPresent
}
StaticSettings --> ModConfigService : uses
ConfigurationController --> ModConfigService : uses
InstallationController --> MuModService : uses
MuModController --> MuModService : uses
ModConfigService --> MuModService : uses
InstallationController --> StaticSettings : uses
MuModService --> ModPaths : uses
ModConfigService --> ModPaths : uses
InstallationController --> ModPaths : uses
InstallationController --> GameModInfo : returns
从前端进行 MuMod 渠道切换与缓存更新的流程图flowchart TD
A["User changes MuMod channel radio in ConfigEditor"] --> B["localMuModChannel updated"]
B --> C["watch(localMuModChannel) triggers"]
C --> D["updateMuModChannel(newChannel) (debounced)"]
D --> E["updateMuModChannelImpl(channel)"]
E --> F["Api.SetMuModChannelAndEnsureCache({ channel })"]
F --> G["MuModController.SetMuModChannelAndEnsureCache"]
G --> H["MuModService.WriteChannel(channel)"]
H --> I["MuModService.EnsureCache()
- resolve version
- download cache
- verify signature
- write cache file"]
I --> J["EnsureCacheResultDto(Success, Version, Error)"]
J --> K["Frontend updateModInfo()
(fetch GameModInfo)"]
K --> L["Frontend updateAquaMaiConfig()
(fetch config via GetAquaMaiConfig)"]
L --> M["UI reflects new channel and cache version"]
F -->|error| N["globalCapture(e, 'mod.cacheFailed')"]
文件级改动
提示与命令与 Sourcery 交互
自定义使用体验打开你的 控制面板 以:
获取帮助Original review guide in EnglishReviewer's GuideIntegrates MuMod as an alternative to AquaMai, adds backend services and APIs to manage MuMod installation and cache, unifies config loading through a shared ModConfigService, and updates the frontend mod manager UI and settings to support MuMod channels, conflict handling, and async rescans. Sequence diagram for installing MuMod from the Mod Manager dropdownsequenceDiagram
actor User
participant ModManagerUI as ModInstallDropdown
participant ApiClient as Api_Client
participant InstallCtrl as InstallationController
participant MuModSvc as MuModService
participant FS as FileSystem
participant VersionApi as Version_API
participant SigVerifier as AquaMaiSignatureV2
User->>ModManagerUI: Click MuMod option in DropMenu
ModManagerUI->>ModManagerUI: doInstall(mumod)
ModManagerUI->>ApiClient: InstallMuMod()
ApiClient->>InstallCtrl: POST InstallMuModApi
InstallCtrl->>FS: CopyFile(MuModDllBuiltinPath, MuModDllInstalledPath)
alt AquaMai.dll exists
InstallCtrl->>FS: Delete(AquaMaiDllInstalledPath)
end
alt MuMod.toml not exists
InstallCtrl->>FS: WriteAllText(MuModConfigPath, default config)
end
InstallCtrl->>MuModSvc: EnsureCache()
MuModSvc->>MuModSvc: GetResolvedCachePath()
MuModSvc->>FS: File.Exists(cachePath)
alt Cache up to date
MuModSvc-->>InstallCtrl: EnsureCacheResult(Success=true, Version, Error=null)
else Cache missing or outdated
MuModSvc->>MuModSvc: ResolveApiType(ReadConfig().Channel)
MuModSvc->>VersionApi: FetchVersionInfosAsync(CosVersionApiUrl)
MuModSvc->>VersionApi: FetchVersionInfosAsync(CfVersionApiUrl)
VersionApi-->>MuModSvc: VersionInfoModel[]
MuModSvc->>MuModSvc: Select VersionInfoModel by Type
MuModSvc->>VersionApi: DownloadFromUrlsAsync(urls)
VersionApi-->>MuModSvc: byte[] cacheData
MuModSvc->>SigVerifier: VerifySignature(cacheData)
SigVerifier-->>MuModSvc: VerifyStatus
MuModSvc->>FS: WriteAllBytesAsync(tempCachePath, cacheData)
MuModSvc->>FS: Move(tempCachePath, cachePath, overwrite=true)
MuModSvc-->>InstallCtrl: EnsureCacheResult(Success, Version, Error)
end
InstallCtrl-->>ApiClient: 200 OK
ApiClient-->>ModManagerUI: Response
ModManagerUI->>ApiClient: GetGameModInfo()
ApiClient-->>ModManagerUI: GameModInfo(muModInstalled=true,...)
ModManagerUI->>ApiClient: GetAquaMaiConfig(forceDefault=false, skipSignatureCheck=false)
ApiClient->>ConfigCtrl: GET GetAquaMaiConfigApi
participant ConfigCtrl as ConfigurationController
ConfigCtrl->>ModConfigSvc: GetAquaMaiDllPath()
participant ModConfigSvc as ModConfigService
ModConfigSvc->>MuModSvc: IsMuModInstalled()
MuModSvc-->>ModConfigSvc: bool
ModConfigSvc->>FS: File.Exists(AquaMaiDllInstalledPath)
alt Only MuMod installed
ModConfigSvc->>MuModSvc: GetResolvedCachePath()
MuModSvc-->>ModConfigSvc: cachePath
ModConfigSvc-->>ConfigCtrl: dllPath=cachePath
else AquaMai installed
ModConfigSvc-->>ConfigCtrl: dllPath=AquaMaiDllInstalledPath
end
ConfigCtrl->>FS: FileStream(dllPath)
ConfigCtrl->>ModConfigSvc: GetCurrentAquaMaiConfig(forceDefault, shouldSkipSignatureCheck)
ModConfigSvc->>FS: ReadAllBytes(dllPath)
ModConfigSvc->>SigVerifier: VerifySignature(binary)
SigVerifier-->>ModConfigSvc: VerifyStatus.Valid
ModConfigSvc->>ModConfigSvc: CheckConfigApiVersion(configInterface)
ModConfigSvc->>FS: File.Exists(AquaMaiConfigPath)
alt Config exists and !forceDefault
ModConfigSvc->>FS: ReadAllText(AquaMaiConfigPath)
ModConfigSvc->>ModConfigSvc: Migrate and Parse config
ModConfigSvc->>StaticSettings: UpdateAssetPathsFromAquaMaiConfig(config)
participant StaticSettings
end
ModConfigSvc-->>ConfigCtrl: IConfig
ConfigCtrl-->>ApiClient: AquaMaiConfigDto
ApiClient-->>ModManagerUI: AquaMaiConfigDto
ModManagerUI->>ModManagerUI: updateModInfo(), updateAquaMaiConfig()
ModManagerUI-->>User: UI shows MuMod installed, channel and cache status
Class diagram for MuMod and unified mod configuration servicesclassDiagram
class MuModService {
-logger ILogger~MuModService~
+MuModService(logger ILogger~MuModService~)
+MuModConfigModel ReadConfig()
+void WriteChannel(channel string)
+string GetResolvedCachePath()
+Task~EnsureCacheResult~ EnsureCache(ct CancellationToken)
+string? GetCacheInfo()
+bool IsMuModInstalled()
+string? GetMuModVersion()
-string ResolveApiType(channel string)
-string? ReadProductVersion(path string)
-string? NormalizeVersion(version string)
-Task~(VersionInfoModel,VersionSource)~ ResolveVersionInfoAsync(apiType string, ct CancellationToken)
-IEnumerable~string~ BuildDownloadUrls(info VersionInfoModel, source VersionSource)
-Task~VersionInfoModel[]~ FetchVersionInfosAsync(url string, ct CancellationToken)
-Task~byte[]~ DownloadFromUrlsAsync(urls IReadOnlyList~string~, ct CancellationToken)
}
class MuModConfigModel {
+string Channel
+string CachePath
}
class ModConfigService {
-MuModService _muModService
+ModConfigService(muModService MuModService)
+void CheckConfigApiVersion(configInterface HeadlessConfigInterface)
+Task~string~ GetAquaMaiDllPath(ct CancellationToken)
+Task~IConfig~ GetCurrentAquaMaiConfig(forceDefault bool, skipSignatureCheck bool, ct CancellationToken)
<<exception>> UnsupportedConfigApiVersionException
<<exception>> ConfigCorruptedException
<<exception>> AquaMaiNotInstalledException
<<exception>> AquaMaiSignatureVerificationFailedException
}
class InstallationController {
-StaticSettings settings
-ILogger~InstallationController~ logger
-MuModService muModService
+InstallationController(settings StaticSettings, logger ILogger~InstallationController~, muModService MuModService)
+GameModInfo GetGameModInfo()
+void InstallMelonLoader()
+void InstallAquaMai()
+Task InstallAquaMaiOnline(req InstallAquaMaiOnlineDto)
+Task InstallMuMod()
+void DeleteAquaMai()
+void DeleteMuMod()
+void KillGameProcess()
}
class ConfigurationController {
-StaticSettings settings
-ILogger~ConfigurationController~ logger
-ModConfigService modConfigService
+ConfigurationController(settings StaticSettings, logger ILogger~ConfigurationController~, modConfigService ModConfigService)
+Task~AquaMaiConfigDto.ConfigDto~ GetAquaMaiConfig(forceDefault bool, skipSignatureCheck bool)
+Task SetAquaMaiConfig(config AquaMaiConfigDto.ConfigSaveDto)
}
class StaticSettings {
-ILogger~StaticSettings~ _logger
-ModConfigService _modConfigService
+StaticSettings(logger ILogger~StaticSettings~, modConfigService ModConfigService)
+Task InitializeGameData()
+Task RescanAll()
+List~MusicXmlWithABJacket~ GetMusicList()
+string GetFreeAssetDir()
+static void UpdateAssetPathsFromAquaMaiConfig(config IConfig)
}
class MuModController {
-MuModService muModService
+MuModController(muModService MuModService)
+MuModConfigDto GetMuModConfig()
+Task~EnsureCacheResultDto~ SetMuModChannelAndEnsureCache(req SetChannelDto)
+Task~EnsureCacheResultDto~ EnsureMuModCache()
}
class ModPaths {
+string AquaMaiConfigPath
+string AquaMaiConfigBackupDirPath
+string AquaMaiDllInstalledPath
+string AquaMaiDllBuiltinPath
+string MuModDllInstalledPath
+string MuModDllBuiltinPath
+string MuModConfigPath
+string MuModDefaultCachePath
}
class GameModInfo {
+bool MelonLoaderInstalled
+bool AquaMaiInstalled
+string AquaMaiVersion
+string BundledAquaMaiVersion
+bool IsVersionTooLow
+bool IsHidConflictExist
+VerifyResult Signature
+bool IsAdxHidIoModAbsent
+bool IsMmlLegacyLibsInstalled
+bool MuModInstalled
+string MuModVersion
+string MuModChannel
+string MuModCacheVersion
+bool IsBothModsPresent
}
StaticSettings --> ModConfigService : uses
ConfigurationController --> ModConfigService : uses
InstallationController --> MuModService : uses
MuModController --> MuModService : uses
ModConfigService --> MuModService : uses
InstallationController --> StaticSettings : uses
MuModService --> ModPaths : uses
ModConfigService --> ModPaths : uses
InstallationController --> ModPaths : uses
InstallationController --> GameModInfo : returns
Flow diagram for MuMod channel change and cache update from frontendflowchart TD
A["User changes MuMod channel radio in ConfigEditor"] --> B["localMuModChannel updated"]
B --> C["watch(localMuModChannel) triggers"]
C --> D["updateMuModChannel(newChannel) (debounced)"]
D --> E["updateMuModChannelImpl(channel)"]
E --> F["Api.SetMuModChannelAndEnsureCache({ channel })"]
F --> G["MuModController.SetMuModChannelAndEnsureCache"]
G --> H["MuModService.WriteChannel(channel)"]
H --> I["MuModService.EnsureCache()
- resolve version
- download cache
- verify signature
- write cache file"]
I --> J["EnsureCacheResultDto(Success, Version, Error)"]
J --> K["Frontend updateModInfo()
(fetch GameModInfo)"]
K --> L["Frontend updateAquaMaiConfig()
(fetch config via GetAquaMaiConfig)"]
L --> M["UI reflects new channel and cache version"]
F -->|error| N["globalCapture(e, 'mod.cacheFailed')"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the application's mod management capabilities by integrating MuMod, an auto-update loader. This integration provides a more dynamic and streamlined approach to managing game modifications, particularly AquaMai. The changes involve a substantial refactoring of existing mod configuration logic into a dedicated service, converting several operations to be asynchronous for better performance, and updating the user interface to support MuMod-specific features and handle potential mod conflicts. The overall goal is to offer users more flexibility and a smoother experience in keeping their game mods up-to-date. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并留下了一些高层次的反馈:
StaticSettings.RescanAll现在会调用_modConfigService.GetCurrentAquaMaiConfig(),而后者内部又会调用StaticSettings.UpdateAssetPathsFromAquaMaiConfig;建议将更新资源路径的责任集中到一个地方,以避免隐式副作用和重复更新。MuModController.SetMuModChannelAndEnsureCache和EnsureMuModCache会捕获所有异常,并始终返回带有Success标志的 HTTP 200;如果调用方需要区分传输错误和逻辑失败,建议返回合适的 HTTP 状态码或更丰富的错误细节,而不是直接吞掉异常。
给 AI Agent 的提示词
Please address the comments from this code review:
## Overall Comments
- StaticSettings.RescanAll now calls _modConfigService.GetCurrentAquaMaiConfig(), which itself calls StaticSettings.UpdateAssetPathsFromAquaMaiConfig; consider centralizing the responsibility for updating asset paths in one place to avoid implicit side effects and double updates.
- MuModController.SetMuModChannelAndEnsureCache and EnsureMuModCache catch all exceptions and always return HTTP 200 with a Success flag; if callers need to distinguish transport errors from logical failures, consider returning appropriate HTTP status codes or richer error details instead of swallowing the exceptions.
## Individual Comments
### Comment 1
<location path="MaiChartManager/Controllers/Mod/MuModController.cs" line_range="21-25" />
<code_context>
+ }
+
+ [HttpPut]
+ public async Task<EnsureCacheResultDto> SetMuModChannelAndEnsureCache([FromBody] SetChannelDto req)
+ {
+ if (req.Channel != "slow" && req.Channel != "fast")
+ {
+ throw new ArgumentException("Channel must be 'slow' or 'fast'", nameof(req));
+ }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid throwing `ArgumentException` for invalid channel and return a controlled error response instead.
Throwing `ArgumentException` here will surface as a 500 and won’t match the `EnsureCacheResultDto` contract the client expects. Instead, either return `BadRequest` with a clear validation message or an `EnsureCacheResultDto` with `Success = false` and an appropriate error message so clients always receive a predictable response shape.
</issue_to_address>帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- StaticSettings.RescanAll now calls _modConfigService.GetCurrentAquaMaiConfig(), which itself calls StaticSettings.UpdateAssetPathsFromAquaMaiConfig; consider centralizing the responsibility for updating asset paths in one place to avoid implicit side effects and double updates.
- MuModController.SetMuModChannelAndEnsureCache and EnsureMuModCache catch all exceptions and always return HTTP 200 with a Success flag; if callers need to distinguish transport errors from logical failures, consider returning appropriate HTTP status codes or richer error details instead of swallowing the exceptions.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- StaticSettings.RescanAll now calls _modConfigService.GetCurrentAquaMaiConfig(), which itself calls StaticSettings.UpdateAssetPathsFromAquaMaiConfig; consider centralizing the responsibility for updating asset paths in one place to avoid implicit side effects and double updates.
- MuModController.SetMuModChannelAndEnsureCache and EnsureMuModCache catch all exceptions and always return HTTP 200 with a Success flag; if callers need to distinguish transport errors from logical failures, consider returning appropriate HTTP status codes or richer error details instead of swallowing the exceptions.
## Individual Comments
### Comment 1
<location path="MaiChartManager/Controllers/Mod/MuModController.cs" line_range="21-25" />
<code_context>
+ }
+
+ [HttpPut]
+ public async Task<EnsureCacheResultDto> SetMuModChannelAndEnsureCache([FromBody] SetChannelDto req)
+ {
+ if (req.Channel != "slow" && req.Channel != "fast")
+ {
+ throw new ArgumentException("Channel must be 'slow' or 'fast'", nameof(req));
+ }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid throwing `ArgumentException` for invalid channel and return a controlled error response instead.
Throwing `ArgumentException` here will surface as a 500 and won’t match the `EnsureCacheResultDto` contract the client expects. Instead, either return `BadRequest` with a clear validation message or an `EnsureCacheResultDto` with `Success = false` and an appropriate error message so clients always receive a predictable response shape.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
本次 PR 成功集成了 MuMod 作为新的模组加载器,这是一个重要的功能增强。整体代码结构清晰,特别是在后端引入了 ModConfigService 和 MuModService 来分离关注点,使得逻辑更加模块化。对网络请求和文件操作的健壮性考虑得非常周到,例如 MuModService 中的 API 回退机制和原子化文件写入。将多个同步操作改造为异步也提升了应用的响应性。
我发现了一些可以改进的地方,主要集中在异步编程的最佳实践和资源管理方面,例如避免在构造函数中同步等待异步任务、正确使用 HttpClient 等。这些修改将有助于提升应用的稳定性和性能。详细建议请见具体的 review comments。
| { | ||
| GetGameVersion(); | ||
| RescanAll(); | ||
| RescanAll().GetAwaiter().GetResult(); |
There was a problem hiding this comment.
| string? muModCacheVersion = null; | ||
| if (muModInstalled) | ||
| { | ||
| try { muModChannel = muModService.ReadConfig().Channel; } catch { } |
There was a problem hiding this comment.
在 GetGameModInfo 方法中,使用了空的 catch 块来静默处理读取 MuMod 配置失败时可能发生的异常。虽然这可以防止程序崩溃,但完全吞掉异常会使调试变得困难,因为无法得知读取失败的原因(例如,文件被占用、权限问题或格式错误)。建议至少在 catch 块中记录一条警告或调试日志,以便于追踪潜在问题。
try { muModChannel = muModService.ReadConfig().Channel; } catch (Exception e) { logger.LogWarning(e, "Failed to read MuMod config channel."); }| public async Task<IConfig> GetCurrentAquaMaiConfig(bool forceDefault = false, bool skipSignatureCheck = false, CancellationToken ct = default) | ||
| { | ||
| var dllPath = await GetAquaMaiDllPath(ct); | ||
|
|
||
| var binary = File.ReadAllBytes(dllPath); | ||
| if (!skipSignatureCheck) | ||
| { | ||
| var sigResult = AquaMaiSignatureV2.VerifySignature(binary); | ||
| if (sigResult.Status != AquaMaiSignatureV2.VerifyStatus.Valid) | ||
| { | ||
| throw new AquaMaiSignatureVerificationFailedException(); | ||
| } | ||
| } | ||
| var configInterface = HeadlessConfigLoader.LoadFromPacked(binary); | ||
| var config = configInterface.CreateConfig(); | ||
| CheckConfigApiVersion(configInterface); | ||
| if (File.Exists(ModPaths.AquaMaiConfigPath) && !forceDefault) | ||
| { | ||
| try | ||
| { | ||
| var view = configInterface.CreateConfigView(File.ReadAllText(ModPaths.AquaMaiConfigPath)); | ||
| var migrationManager = configInterface.GetConfigMigrationManager(); | ||
|
|
||
| if (migrationManager.GetVersion(view) != migrationManager.LatestVersion) | ||
| { | ||
| Console.WriteLine("Migrating AquaMai config from {0} to {1}", migrationManager.GetVersion(view), migrationManager.LatestVersion); | ||
| view = migrationManager.Migrate(view); | ||
| } | ||
|
|
||
| var parser = configInterface.GetConfigParser(); | ||
| parser.Parse(config, view); | ||
| StaticSettings.UpdateAssetPathsFromAquaMaiConfig(config); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Console.WriteLine("无法加载 AquaMai 配置"); | ||
| Console.WriteLine(ex); | ||
| if (ex.Message.Contains("Could not migrate the config")) | ||
| { | ||
| // 这个应该是,AquaMai 未安装或需要更新 | ||
| throw; | ||
| } | ||
| // 这个的提示是 AquaMai 配置文件损坏 | ||
| throw new ConfigCorruptedException(); | ||
| } | ||
| } | ||
|
|
||
| return config; | ||
| } |
There was a problem hiding this comment.
GetCurrentAquaMaiConfig 方法被标记为 async,但其内部的文件读取操作 File.ReadAllBytes 和 File.ReadAllText 却是同步的。这会阻塞执行线程,降低了异步的优势。为了充分利用异步编程,建议改用 File.ReadAllBytesAsync 和 File.ReadAllTextAsync,并传递 CancellationToken 以支持取消操作。
public async Task<IConfig> GetCurrentAquaMaiConfig(bool forceDefault = false, bool skipSignatureCheck = false, CancellationToken ct = default)
{
var dllPath = await GetAquaMaiDllPath(ct);
var binary = await File.ReadAllBytesAsync(dllPath, ct);
if (!skipSignatureCheck)
{
var sigResult = AquaMaiSignatureV2.VerifySignature(binary);
if (sigResult.Status != AquaMaiSignatureV2.VerifyStatus.Valid)
{
throw new AquaMaiSignatureVerificationFailedException();
}
}
var configInterface = HeadlessConfigLoader.LoadFromPacked(binary);
var config = configInterface.CreateConfig();
CheckConfigApiVersion(configInterface);
if (File.Exists(ModPaths.AquaMaiConfigPath) && !forceDefault)
{
try
{
var view = configInterface.CreateConfigView(await File.ReadAllTextAsync(ModPaths.AquaMaiConfigPath, ct));
var migrationManager = configInterface.GetConfigMigrationManager();
if (migrationManager.GetVersion(view) != migrationManager.LatestVersion)
{
Console.WriteLine("Migrating AquaMai config from {0} to {1}", migrationManager.GetVersion(view), migrationManager.LatestVersion);
view = migrationManager.Migrate(view);
}
var parser = configInterface.GetConfigParser();
parser.Parse(config, view);
StaticSettings.UpdateAssetPathsFromAquaMaiConfig(config);
}
catch (Exception ex)
{
Console.WriteLine("无法加载 AquaMai 配置");
Console.WriteLine(ex);
if (ex.Message.Contains("Could not migrate the config"))
{
// 这个应该是,AquaMai 未安装或需要更新
throw;
}
// 这个的提示是 AquaMai 配置文件损坏
throw new ConfigCorruptedException();
}
}
return config;
}| private static async Task<VersionInfoModel[]> FetchVersionInfosAsync(string url, CancellationToken ct) | ||
| { | ||
| using var client = new HttpClient | ||
| { | ||
| Timeout = TimeSpan.FromSeconds(15) | ||
| }; | ||
|
|
||
| var json = await client.GetStringAsync(url, ct); | ||
| var result = JsonSerializer.Deserialize<VersionInfoModel[]>(json, new JsonSerializerOptions | ||
| { | ||
| PropertyNameCaseInsensitive = true | ||
| }); | ||
|
|
||
| return result ?? []; | ||
| } |
There was a problem hiding this comment.
在 FetchVersionInfosAsync 和 DownloadFromUrlsAsync 方法中,每次调用都创建了一个新的 HttpClient 实例。这是一种反模式,在高并发下可能导致套接字耗尽(socket exhaustion)。推荐通过依赖注入使用 IHttpClientFactory 来创建和管理 HttpClient 实例,这样可以更好地重用底层连接。
另外,在 ResolveVersionInfoAsync 中通过 CancellationTokenSource 设置了超时,同时在 FetchVersionInfosAsync 和 DownloadFromUrlsAsync 的 HttpClient 上也设置了 Timeout 属性。这两种超时机制是冗余的。建议移除 HttpClient.Timeout 设置,统一使用 CancellationToken 来控制超时,这样代码意图更清晰。
Summary by Sourcery
集成 MuMod 作为 AquaMai 的替代/补充模组加载器,包括后端服务、配置处理以及前端 UI 支持,并使扫描与初始化流程支持异步感知。
新特性:
增强:
ModConfigService,该服务可以从 AquaMai 或 MuMod 缓存中获取配置,并复用签名/API 版本检查逻辑。构建:
tsx替代ts-node,并重命名生成的 API 标题以区分该客户端。杂务:
Original summary in English
Summary by Sourcery
Integrate MuMod as an alternative/companion mod loader alongside AquaMai, including backend services, configuration handling, and frontend UI support, while making scanning and initialization flows async-aware.
New Features:
Enhancements:
Build:
Chores: