🦄 refactor: 下载管理接入 Rust 目录扫描 & 任务栏歌词跟随歌曲封面颜色#864
Conversation
|
/gemini review |
|
/gemini review |
|
/gemini review |
There was a problem hiding this comment.
Code Review
本次 PR 包含两项主要变更:为下载管理接入了基于 Rust 的目录扫描功能,以及实现了任务栏歌词颜色跟随歌曲封面颜色的功能。代码重构做得很好,特别是 LocalMusicService 中扫描逻辑的拆分和数据格式化的集中处理,使得代码更清晰、更模块化。
我的主要反馈是关于类型安全性的一个退步。在几处代码中,用于 IPC 传输的歌曲数据的特定类型被替换为了 Record<string, unknown>[]。虽然这可能解决了眼前的类型不匹配问题,但从长远来看,这会降低代码的安全性和可维护性。我建议为这些数据重新引入 DTO。此外,我还发现了一个在 IPC handler 中可以提升类型安全的小机会。
总的来说,这是一次很棒的 PR,带来了显著的改进。
|
@copilot 看看💩代码 |
There was a problem hiding this comment.
Pull request overview
该 PR 主要将下载管理与本地音乐扫描逻辑接入 Rust 扫描/本地库 DB,并让任务栏歌词颜色跟随歌曲封面主色;同时统一了歌曲 size 字段为字节并改进前端展示。
Changes:
- 下载目录/本地库扫描改为通过主进程 LocalMusicService + Rust 扫描结果回传,并复用 DB 查询。
- 任务栏歌词主题色逻辑调整为读取封面主题色并发送到任务栏侧。
- 统一
size为字节:后端/接口不再提前换算 MB,前端使用formatFileSize统一展示。
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/views/Local/layout.vue | 本地库同步接收数据直接交给 formatSongsList,并调整总容量计算口径/单位。 |
| src/views/Download/layout.vue | 下载管理页面模板格式调整(无核心逻辑变化)。 |
| src/views/Download/downloaded.vue | 下载完成列表不再强制隐藏封面。 |
| src/utils/initIpc.ts | 任务栏请求数据时补发封面主色。 |
| src/utils/helper.ts | handleSongQuality 增加提前返回逻辑(当前实现存在潜在行为回退问题)。 |
| src/utils/color.ts | 将“任务栏主题色”改为“封面颜色”发送逻辑,并从 statusStore.songCoverTheme 读取。 |
| src/core/resource/DownloadManager.ts | 下载完成列表改用新 IPC get-downloaded-songs 获取并交给 formatSongsList。 |
| src/components/Setting/config/lyric.ts | 设置文案从“跟随主题色”改为“跟随封面颜色”。 |
| src/components/Player/FullPlayer.vue | 增加 mousemove/click 触发 playerMove,改善自动隐藏交互。 |
| src/components/Card/SongCard.vue | 歌曲大小展示改为 formatFileSize(字节制)。 |
| src/api/streaming/subsonic.ts | Subsonic 歌曲 size 不再转换为 MB,保持字节。 |
| electron/main/utils/format.ts | 新增 processMusicList:统一补齐 name/cover/quality 等字段供渲染进程使用。 |
| electron/main/services/MusicMetadataService.ts | 为 setMetadata 引入明确的输入类型 MusicMetadataInput。 |
| electron/main/services/LocalMusicService.ts | 将扫描逻辑抽到私有 _scan,新增 scanDirectory 支持下载目录扫描。 |
| electron/main/ipc/ipc-file.ts | 新增 get-downloaded-songs IPC,并复用 processMusicList;封面目录路径提取为函数。 |
| electron/main/database/LocalMusicDB.ts | 新增按目录前缀查询 getTracksInPath,用于下载目录扫描后查询。 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const settingStore = useSettingStore(); | ||
| const { disableAiAudio } = settingStore; | ||
|
|
||
| if (!song) return undefined; |
There was a problem hiding this comment.
handleSongQuality 里用 if (!song) return undefined; 会把 0(例如本地 bitrate 为空时用 0 兜底)也当成无效输入,导致本地歌曲音质从原本的 LQ 变成 undefined,从而不显示音质标签/影响逻辑。建议改为仅对 null/undefined 做提前返回(例如 song == null),保留数字 0 的正常分支处理。
| if (!song) return undefined; | |
| if (song == null) return undefined; |
| ipcMain.handle("set-music-metadata", async (_, path: string, metadata) => { | ||
| return musicMetadataService.setMetadata(path, metadata); | ||
| }); |
There was a problem hiding this comment.
这里 metadata 参数没有类型注解,会在开启 noImplicitAny/strict 时变成隐式 any,导致 node 端 typecheck 失败。建议把参数类型显式标注为 MusicMetadataInput(可从 MusicMetadataService 导出/引入),或至少标注为 unknown 并在 setMetadata 内做校验/转换。
| ipcMain.handle("set-music-metadata", async (_, path: string, metadata) => { | |
| return musicMetadataService.setMetadata(path, metadata); | |
| }); | |
| ipcMain.handle( | |
| "set-music-metadata", | |
| async (_, path: string, metadata: Parameters<MusicMetadataService["setMetadata"]>[1]) => { | |
| return musicMetadataService.setMetadata(path, metadata); | |
| }, | |
| ); |
| case "end": | ||
| if (event.deletedPaths && event.deletedPaths.length > 0) { | ||
| if (!ignoreDelete && event.deletedPaths && event.deletedPaths.length > 0) { | ||
| this.db?.deleteTracks(event.deletedPaths); | ||
| } | ||
| resolve(); |
There was a problem hiding this comment.
scanDirectory() 通过 ignoreDelete=true 完全跳过了 deletedPaths 的处理,这会导致扫描目录内被删除/移动的文件仍然残留在 DB 中,getTracksInPath() 也会继续返回这些陈旧记录(例如下载目录里手动删歌后列表仍显示)。建议在 ignoreDelete 场景下仅过滤并删除属于本次扫描目录前缀的 deletedPaths(避免误删其它库目录),而不是全部忽略。
| // 全部音乐大小 | ||
| const allMusicSize = computed<number>(() => { | ||
| const total = listData.value.reduce((total, song) => (total += song?.size || 0), 0); | ||
| return Number((total / 1024).toFixed(2)); | ||
| const totalBytes = listData.value.reduce((total, song) => (total += song?.size || 0), 0); | ||
| return Number((totalBytes / (1024 * 1024 * 1024)).toFixed(2)); | ||
| }); |
There was a problem hiding this comment.
allMusicSize 实际上是基于 listData 计算的(会受搜索/文件夹筛选影响),但注释现在写成“全部音乐大小”,容易误导后续维护。建议把注释改回“基于当前筛选结果的大小”,或改为使用 localStore.localSongs/未筛选数据来真正表示“全部”。
| @@ -400,13 +386,13 @@ const getAllLocalMusic = debounce( | |||
| // 记录初始歌曲数量,用于计算新增数量 | |||
| const initialSongCount = localStore.localSongs.length; | |||
| // 累积接收到的tracks | |||
| const receivedTracks: MusicTrackData[] = []; | |||
| const receivedTracks: Record<string, unknown>[] = []; | |||
| let isCompleted = false; | |||
| // 清理之前的监听器 | |||
| window.electron.ipcRenderer.removeAllListeners("music-sync-tracks-batch"); | |||
| window.electron.ipcRenderer.removeAllListeners("music-sync-complete"); | |||
| // 监听批量track数据 | |||
| const tracksBatchHandler = (_event: unknown, tracks: MusicTrackData[]) => { | |||
| const tracksBatchHandler = (_event: unknown, tracks: Record<string, unknown>[]) => { | |||
| if (!loading.value || isCompleted) return; | |||
There was a problem hiding this comment.
本地同步的 tracks/receivedTracks 改成了 Record<string, unknown>[],会让 formatSongsList(sourceTracks) 依赖的字段(如 id/name/artist/album/path/size/quality)完全失去编译期约束,后续 IPC 结构一变更就只能在运行时爆雷。建议定义一个明确的 IPC Track 类型(可复用 native/tools 的 MusicTrack 字段并补充 name/quality/cover 等映射字段),并在这里使用该类型而不是 Record<string, unknown>。
No description provided.