import fs from 'node:fs/promises'; import path from 'node:path'; import type { MediaAsset, Project } from '../../shared/types'; import type { AssetId } from '../../shared/types/ids'; /** Все asset id, на которые есть ссылки из сцен (превью, видео, аудио). */ export function collectReferencedAssetIds(p: Project): Set { const refs = new Set(); for (const sc of Object.values(p.scenes)) { if (sc.previewAssetId) refs.add(sc.previewAssetId); for (const vid of sc.media.videos) refs.add(vid); for (const au of sc.media.audios) refs.add(au.assetId); } return refs; } /** * Удаляет с диска файлы снятых материалов и записи в `assets`, на которые больше нет ссылок. */ export async function reconcileAssetFiles(prev: Project, next: Project, cacheDir: string): Promise { const prevIds = new Set(Object.keys(prev.assets) as AssetId[]); const nextIds = new Set(Object.keys(next.assets) as AssetId[]); for (const id of prevIds) { if (nextIds.has(id)) continue; const a = prev.assets[id]; if (a) { const abs = path.join(cacheDir, a.relPath); await fs.unlink(abs).catch(() => undefined); } } const refs = collectReferencedAssetIds(next); const assets = next.assets; const kept: Record = {} as Record; let droppedOrphans = false; for (const id of Object.keys(assets) as AssetId[]) { const a = assets[id]; if (!a) continue; if (refs.has(id)) { kept[id] = a; } else { droppedOrphans = true; const abs = path.join(cacheDir, a.relPath); await fs.unlink(abs).catch(() => undefined); } } return droppedOrphans ? { ...next, assets: kept } : next; }