mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-19 03:57:28 +08:00
fix(local-music): 扫描后清理 AudioCovers 残留封面
封面落盘后,歌曲被删/移出库时对应封面文件不会被回收,长期累积占用磁盘。 新增 prune-local-music-covers IPC:全量扫描完成后渲染进程传入当前所有有效 coverPath,主进程按 basename 比对删除目录内孤儿文件,仅动 readdir 出来的 目录内文件、失败不影响扫描结果。 https://claude.ai/code/session_01LgUk5QMQsXYa7ZFTYpqeLu
This commit is contained in:
@@ -364,4 +364,34 @@ export function initializeLocalMusicScanner(): void {
|
|||||||
return [];
|
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 || '清理失败' };
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+4
@@ -44,6 +44,10 @@ interface API {
|
|||||||
parseLocalMusicMetadata: (
|
parseLocalMusicMetadata: (
|
||||||
filePaths: string[]
|
filePaths: string[]
|
||||||
) => Promise<import('../renderer/types/localMusic').LocalMusicMeta[]>;
|
) => Promise<import('../renderer/types/localMusic').LocalMusicMeta[]>;
|
||||||
|
/** 清理 AudioCovers 目录里不再被引用的残留封面文件 */
|
||||||
|
pruneLocalMusicCovers: (
|
||||||
|
validCoverPaths: string[]
|
||||||
|
) => Promise<{ removed: number } | { error: string }>;
|
||||||
// Download manager
|
// Download manager
|
||||||
downloadAdd: (task: any) => Promise<string>;
|
downloadAdd: (task: any) => Promise<string>;
|
||||||
downloadAddBatch: (tasks: any) => Promise<{ batchId: string; taskIds: string[] }>;
|
downloadAddBatch: (tasks: any) => Promise<{ batchId: string; taskIds: string[] }>;
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ const api = {
|
|||||||
ipcRenderer.invoke('scan-local-music-with-stats', folderPath),
|
ipcRenderer.invoke('scan-local-music-with-stats', folderPath),
|
||||||
parseLocalMusicMetadata: (filePaths: string[]) =>
|
parseLocalMusicMetadata: (filePaths: string[]) =>
|
||||||
ipcRenderer.invoke('parse-local-music-metadata', filePaths),
|
ipcRenderer.invoke('parse-local-music-metadata', filePaths),
|
||||||
|
pruneLocalMusicCovers: (validCoverPaths: string[]) =>
|
||||||
|
ipcRenderer.invoke('prune-local-music-covers', validCoverPaths),
|
||||||
|
|
||||||
// Download manager
|
// Download manager
|
||||||
downloadAdd: (task: any) => ipcRenderer.invoke('download:add', task),
|
downloadAdd: (task: any) => ipcRenderer.invoke('download:add', task),
|
||||||
|
|||||||
@@ -190,6 +190,18 @@ export const useLocalMusicStore = defineStore(
|
|||||||
|
|
||||||
// 5. 从 IndexedDB 重新加载完整列表
|
// 5. 从 IndexedDB 重新加载完整列表
|
||||||
musicList.value = await localDB.getAllData(LOCAL_MUSIC_STORE);
|
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) {
|
} catch (error) {
|
||||||
console.error('扫描本地音乐失败:', error);
|
console.error('扫描本地音乐失败:', error);
|
||||||
message.error('扫描本地音乐失败');
|
message.error('扫描本地音乐失败');
|
||||||
|
|||||||
Vendored
+4
@@ -28,6 +28,10 @@ export interface IElectronAPI {
|
|||||||
) => Promise<{ files: { path: string; modifiedTime: number }[]; count: number }>;
|
) => Promise<{ files: { path: string; modifiedTime: number }[]; count: number }>;
|
||||||
/** 批量解析本地音乐文件元数据 */
|
/** 批量解析本地音乐文件元数据 */
|
||||||
parseLocalMusicMetadata: (_filePaths: string[]) => Promise<LocalMusicMeta[]>;
|
parseLocalMusicMetadata: (_filePaths: string[]) => Promise<LocalMusicMeta[]>;
|
||||||
|
/** 清理 AudioCovers 目录里不再被引用的残留封面文件 */
|
||||||
|
pruneLocalMusicCovers: (
|
||||||
|
_validCoverPaths: string[]
|
||||||
|
) => Promise<{ removed: number } | { error: string }>;
|
||||||
// Download manager
|
// Download manager
|
||||||
downloadAdd: (_task: any) => Promise<string>;
|
downloadAdd: (_task: any) => Promise<string>;
|
||||||
downloadAddBatch: (_tasks: any) => Promise<{ batchId: string; taskIds: string[] }>;
|
downloadAddBatch: (_tasks: any) => Promise<{ batchId: string; taskIds: string[] }>;
|
||||||
|
|||||||
Reference in New Issue
Block a user