Skip to content

Commit ed13243

Browse files
committed
✨ feat: 优化本地音乐文件处理,增加分批扫描以防内存溢出
1 parent cb2727f commit ed13243

1 file changed

Lines changed: 85 additions & 80 deletions

File tree

electron/main/services/LocalMusicService.ts

Lines changed: 85 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -223,90 +223,95 @@ export class LocalMusicService {
223223
}
224224
};
225225

226-
// 处理文件 (新增/更新)
227-
const tasks = entries.map((entry) => {
228-
return this.limit(async () => {
229-
const filePath = entry.path;
230-
const stats = entry.stats;
231-
if (!stats) return;
232-
/** 修改时间 */
233-
const mtime = stats.mtimeMs;
234-
/** 文件大小 */
235-
const size = stats.size;
236-
// 小于 1MB 的文件不处理
237-
if (size < 1024 * 1024) return;
238-
scannedPaths.add(filePath);
239-
/** 缓存 */
240-
const cached = this.db.tracks[filePath];
241-
// 判断是否可以使用缓存
242-
let useCache = false;
243-
if (cached && cached.mtime === mtime && cached.size === size) {
244-
useCache = true;
245-
// 额外检查:如果记录中有封面,验证封面文件是否真实存在
246-
if (cached.cover && !existsSync(join(coverDir, cached.cover))) {
247-
useCache = false;
226+
// 分批处理扫描任务,避免内存溢出
227+
const PROCESS_BATCH_SIZE = 200; // 每批处理200个文件
228+
for (let i = 0; i < entries.length; i += PROCESS_BATCH_SIZE) {
229+
const chunk = entries.slice(i, i + PROCESS_BATCH_SIZE);
230+
const tasks = chunk.map((entry) => {
231+
return this.limit(async () => {
232+
const filePath = entry.path;
233+
const stats = entry.stats;
234+
if (!stats) return;
235+
/** 修改时间 */
236+
const mtime = stats.mtimeMs;
237+
/** 文件大小 */
238+
const size = stats.size;
239+
// 小于 1MB 的文件不处理
240+
if (size < 1024 * 1024) return;
241+
scannedPaths.add(filePath);
242+
/** 缓存 */
243+
const cached = this.db.tracks[filePath];
244+
// 判断是否可以使用缓存
245+
let useCache = false;
246+
if (cached && cached.mtime === mtime && cached.size === size) {
247+
useCache = true;
248+
// 额外检查:如果记录中有封面,验证封面文件是否真实存在
249+
if (cached.cover && !existsSync(join(coverDir, cached.cover))) {
250+
useCache = false;
251+
}
248252
}
249-
}
250-
// 只有当缓存存在 && 修改时间没变 && 文件大小没变 && 封面存在 -> 才跳过
251-
if (useCache) {
252-
processedCount++;
253-
// 添加到批量缓冲区
254-
tracksBuffer.push(cached!);
255-
// 达到批量大小,发送一批
256-
if (tracksBuffer.length >= BATCH_SIZE) {
257-
flushBatch();
258-
}
259-
// 节流发送进度
260-
if (processedCount % 10 === 0 || processedCount === totalFiles) {
261-
onProgress?.(processedCount, totalFiles);
262-
}
263-
return;
264-
}
265-
// 解析元数据
266-
try {
267-
const id = this.getFileId(filePath);
268-
const metadata = await parseFile(filePath);
269-
// 过滤规则
270-
// 时长 < 30s
271-
if (metadata.format.duration && metadata.format.duration < 30) return;
272-
// 时长 > 2h (7200s)
273-
if (metadata.format.duration && metadata.format.duration > 7200) return;
274-
// 提取封面
275-
const coverPath = await this.extractCover(metadata, id);
276-
// 构建音乐数据
277-
const track: MusicTrack = {
278-
id,
279-
path: filePath,
280-
title: metadata.common.title || basename(filePath),
281-
artist: metadata.common.artist || "Unknown Artist",
282-
album: metadata.common.album || "Unknown Album",
283-
duration: (metadata.format.duration || 0) * 1000,
284-
mtime,
285-
size,
286-
cover: coverPath,
287-
bitrate: metadata.format.bitrate ?? 0,
288-
};
289-
// 添加到数据库
290-
this.db.tracks[filePath] = track;
291-
isDirty = true;
292-
// 添加到批量缓冲区
293-
tracksBuffer.push(track);
294-
// 达到批量大小,发送一批
295-
if (tracksBuffer.length >= BATCH_SIZE) {
296-
flushBatch();
253+
// 只有当缓存存在 && 修改时间没变 && 文件大小没变 && 封面存在 -> 才跳过
254+
if (useCache) {
255+
processedCount++;
256+
// 添加到批量缓冲区
257+
tracksBuffer.push(cached!);
258+
// 达到批量大小,发送一批
259+
if (tracksBuffer.length >= BATCH_SIZE) {
260+
flushBatch();
261+
}
262+
// 节流发送进度
263+
if (processedCount % 10 === 0 || processedCount === totalFiles) {
264+
onProgress?.(processedCount, totalFiles);
265+
}
266+
return;
297267
}
298-
} catch (err) {
299-
console.warn(`Parse error [${filePath}]:`, err);
300-
} finally {
301-
processedCount++;
302-
// 节流发送进度
303-
if (processedCount % 10 === 0 || processedCount === totalFiles) {
304-
onProgress?.(processedCount, totalFiles);
268+
// 解析元数据
269+
try {
270+
const id = this.getFileId(filePath);
271+
const metadata = await parseFile(filePath);
272+
// 过滤规则
273+
// 时长 < 30s
274+
if (metadata.format.duration && metadata.format.duration < 30) return;
275+
// 时长 > 2h (7200s)
276+
if (metadata.format.duration && metadata.format.duration > 7200) return;
277+
// 提取封面
278+
const coverPath = await this.extractCover(metadata, id);
279+
// 构建音乐数据
280+
const track: MusicTrack = {
281+
id,
282+
path: filePath,
283+
title: metadata.common.title || basename(filePath),
284+
artist: metadata.common.artist || "Unknown Artist",
285+
album: metadata.common.album || "Unknown Album",
286+
duration: (metadata.format.duration || 0) * 1000,
287+
mtime,
288+
size,
289+
cover: coverPath,
290+
bitrate: metadata.format.bitrate ?? 0,
291+
};
292+
// 添加到数据库
293+
this.db.tracks[filePath] = track;
294+
isDirty = true;
295+
// 添加到批量缓冲区
296+
tracksBuffer.push(track);
297+
// 达到批量大小,发送一批
298+
if (tracksBuffer.length >= BATCH_SIZE) {
299+
flushBatch();
300+
}
301+
} catch (err) {
302+
console.warn(`Parse error [${filePath}]:`, err);
303+
} finally {
304+
processedCount++;
305+
// 节流发送进度
306+
if (processedCount % 10 === 0 || processedCount === totalFiles) {
307+
onProgress?.(processedCount, totalFiles);
308+
}
305309
}
306-
}
310+
});
307311
});
308-
});
309-
await Promise.all(tasks);
312+
// 等待当前批次完成,释放闭包引用,避免内存积压
313+
await Promise.all(tasks);
314+
}
310315
// 发送最后一批数据
311316
flushBatch();
312317
// 清理脏数据 (处理文件删除 或 移除文件夹的情况)

0 commit comments

Comments
 (0)