a6cbcc273e
Made-with: Cursor
52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
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<AssetId> {
|
|
const refs = new Set<AssetId>();
|
|
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<Project> {
|
|
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<AssetId, MediaAsset> = {} as Record<AssetId, MediaAsset>;
|
|
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;
|
|
}
|