From 4e59de9fbb4b781b30ab664d91f6e6677ab6c0cd Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 02:22:31 +0000 Subject: [PATCH] =?UTF-8?q?fix(local-music):=20=E6=89=AB=E6=8F=8F=E5=90=8E?= =?UTF-8?q?=E6=B8=85=E7=90=86=20AudioCovers=20=E6=AE=8B=E7=95=99=E5=B0=81?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 封面落盘后,歌曲被删/移出库时对应封面文件不会被回收,长期累积占用磁盘。 新增 prune-local-music-covers IPC:全量扫描完成后渲染进程传入当前所有有效 coverPath,主进程按 basename 比对删除目录内孤儿文件,仅动 readdir 出来的 目录内文件、失败不影响扫描结果。 https://claude.ai/code/session_01LgUk5QMQsXYa7ZFTYpqeLu --- src/main/modules/localMusicScanner.ts | 30 ++++++++++++++++++++++++ src/preload/index.d.ts | 4 ++++ src/preload/index.ts | 2 ++ src/renderer/store/modules/localMusic.ts | 12 ++++++++++ src/renderer/types/electron.d.ts | 4 ++++ 5 files changed, 52 insertions(+) diff --git a/src/main/modules/localMusicScanner.ts b/src/main/modules/localMusicScanner.ts index 330dfe9..56889ac 100644 --- a/src/main/modules/localMusicScanner.ts +++ b/src/main/modules/localMusicScanner.ts @@ -364,4 +364,34 @@ export function initializeLocalMusicScanner(): void { return []; } }); + + // 清理 AudioCovers 目录里不再被任何条目引用的残留封面文件。 + // 渲染进程在完成一次全量扫描后传入当前所有有效 coverPath,主进程按 basename 比对, + // 删除集合外的孤儿文件(歌曲被删/移出库后落下的封面)。只动 readdir 出来的目录内 + // 文件,无路径穿越风险。 + ipcMain.handle('prune-local-music-covers', async (_, validCoverPaths: string[]) => { + try { + const dir = getCoverDir(); + const valid = new Set( + (validCoverPaths || []).filter(Boolean).map((p) => path.basename(p)) + ); + const names = await fs.promises.readdir(dir).catch(() => [] as string[]); + let removed = 0; + await Promise.all( + names.map(async (name) => { + if (valid.has(name)) return; + try { + await fs.promises.unlink(path.join(dir, name)); + removed += 1; + } catch (error) { + console.error('清理残留封面失败:', name, error); + } + }) + ); + return { removed }; + } catch (error: any) { + console.error('清理残留封面出错:', error); + return { error: error?.message || '清理失败' }; + } + }); } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 199149c..5d03949 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -44,6 +44,10 @@ interface API { parseLocalMusicMetadata: ( filePaths: string[] ) => Promise; + /** 清理 AudioCovers 目录里不再被引用的残留封面文件 */ + pruneLocalMusicCovers: ( + validCoverPaths: string[] + ) => Promise<{ removed: number } | { error: string }>; // Download manager downloadAdd: (task: any) => Promise; downloadAddBatch: (tasks: any) => Promise<{ batchId: string; taskIds: string[] }>; diff --git a/src/preload/index.ts b/src/preload/index.ts index 0c7b664..4473107 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -83,6 +83,8 @@ const api = { ipcRenderer.invoke('scan-local-music-with-stats', folderPath), parseLocalMusicMetadata: (filePaths: string[]) => ipcRenderer.invoke('parse-local-music-metadata', filePaths), + pruneLocalMusicCovers: (validCoverPaths: string[]) => + ipcRenderer.invoke('prune-local-music-covers', validCoverPaths), // Download manager downloadAdd: (task: any) => ipcRenderer.invoke('download:add', task), diff --git a/src/renderer/store/modules/localMusic.ts b/src/renderer/store/modules/localMusic.ts index a4d5ba6..39c81b0 100644 --- a/src/renderer/store/modules/localMusic.ts +++ b/src/renderer/store/modules/localMusic.ts @@ -190,6 +190,18 @@ export const useLocalMusicStore = defineStore( // 5. 从 IndexedDB 重新加载完整列表 musicList.value = await localDB.getAllData(LOCAL_MUSIC_STORE); + + // 6. 清理 AudioCovers 目录里不再被任何条目引用的残留封面。 + // musicList 此刻已是全量权威列表,传入所有有效 coverPath;歌曲被删/移出库后 + // 落单的封面文件由主进程按 basename 比对删除。失败不影响扫描结果。 + try { + const validCoverPaths = musicList.value + .map((entry) => entry.coverPath) + .filter((p): p is string => !!p); + await window.api.pruneLocalMusicCovers(validCoverPaths); + } catch (error) { + console.error('清理残留封面失败:', error); + } } catch (error) { console.error('扫描本地音乐失败:', error); message.error('扫描本地音乐失败'); diff --git a/src/renderer/types/electron.d.ts b/src/renderer/types/electron.d.ts index 1672367..0a5b9c9 100644 --- a/src/renderer/types/electron.d.ts +++ b/src/renderer/types/electron.d.ts @@ -28,6 +28,10 @@ export interface IElectronAPI { ) => Promise<{ files: { path: string; modifiedTime: number }[]; count: number }>; /** 批量解析本地音乐文件元数据 */ parseLocalMusicMetadata: (_filePaths: string[]) => Promise; + /** 清理 AudioCovers 目录里不再被引用的残留封面文件 */ + pruneLocalMusicCovers: ( + _validCoverPaths: string[] + ) => Promise<{ removed: number } | { error: string }>; // Download manager downloadAdd: (_task: any) => Promise; downloadAddBatch: (_tasks: any) => Promise<{ batchId: string; taskIds: string[] }>;