fix(recordings): fail closed during prune project scans

This commit is contained in:
wiiiii123
2026-04-20 02:03:45 +07:00
parent f7e3494ee3
commit 3509ecf21c
2 changed files with 79 additions and 34 deletions
+47 -5
View File
@@ -57,7 +57,7 @@ describe("pruneAutoRecordings", () => {
await fs.mkdir(projectsDir, { recursive: true });
const recordingPaths: string[] = [];
for (let index = 0; index < 22; index += 1) {
for (let index = 0; index < 23; index += 1) {
const recordingPath = path.join(recordingsDir, `recording-${index}.mp4`);
recordingPaths.push(recordingPath);
await fs.writeFile(recordingPath, `video-${index}`);
@@ -65,14 +65,31 @@ describe("pruneAutoRecordings", () => {
await fs.utimes(recordingPath, timestamp, timestamp);
}
const protectedRecordingPath = recordingPaths.at(-2);
const protectedVideoRecordingPath = recordingPaths.at(-3);
const protectedWebcamRecordingPath = recordingPaths.at(-2);
const prunableRecordingPath = recordingPaths.at(-1);
await fs.writeFile(
path.join(projectsDir, `saved-project.${PROJECT_FILE_EXTENSION}`),
path.join(projectsDir, `saved-project-video.${PROJECT_FILE_EXTENSION}`),
JSON.stringify(
{
videoPath: protectedRecordingPath,
videoPath: protectedVideoRecordingPath,
},
null,
2,
),
"utf-8",
);
await fs.writeFile(
path.join(projectsDir, `saved-project-webcam.${PROJECT_FILE_EXTENSION}`),
JSON.stringify(
{
editor: {
webcam: {
sourcePath: protectedWebcamRecordingPath,
},
},
},
null,
2,
@@ -82,7 +99,32 @@ describe("pruneAutoRecordings", () => {
await pruneAutoRecordings();
await expect(fs.access(protectedRecordingPath!)).resolves.toBeUndefined();
await expect(fs.access(protectedVideoRecordingPath!)).resolves.toBeUndefined();
await expect(fs.access(protectedWebcamRecordingPath!)).resolves.toBeUndefined();
await expect(fs.access(prunableRecordingPath!)).rejects.toThrow();
});
it("aborts pruning when a saved project cannot be parsed", async () => {
const { getRecordingsDir } = await import("../utils");
const { PROJECTS_DIRECTORY_NAME, PROJECT_FILE_EXTENSION } = await import("../constants");
const { pruneAutoRecordings } = await import("./prune");
const recordingsDir = await getRecordingsDir();
const projectsDir = path.join(recordingsDir, PROJECTS_DIRECTORY_NAME);
await fs.mkdir(projectsDir, { recursive: true });
const recordingPath = path.join(recordingsDir, "recording-stale.mp4");
await fs.writeFile(recordingPath, "video");
const staleTimestamp = new Date(Date.now() - 48 * 60 * 60 * 1_000);
await fs.utimes(recordingPath, staleTimestamp, staleTimestamp);
await fs.writeFile(
path.join(projectsDir, `broken-project.${PROJECT_FILE_EXTENSION}`),
"{ invalid json",
"utf-8",
);
await expect(pruneAutoRecordings()).rejects.toThrow();
await expect(fs.access(recordingPath)).resolves.toBeUndefined();
});
});
+32 -29
View File
@@ -46,7 +46,18 @@ async function loadSavedProjectMediaPaths() {
...LEGACY_PROJECT_FILE_EXTENSIONS,
]);
const projectEntries = await fs.readdir(projectsDir, { withFileTypes: true }).catch(() => []);
let projectEntries: Array<{ isFile(): boolean; name: string }>;
try {
projectEntries = await fs.readdir(projectsDir, { withFileTypes: true });
} catch (error) {
const code =
typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
if (code === "ENOENT") {
return protectedPaths;
}
throw error;
}
await Promise.all(
projectEntries
@@ -60,37 +71,29 @@ async function loadSavedProjectMediaPaths() {
})
.map(async (entry) => {
const projectPath = path.join(projectsDir, entry.name);
const rawProject = JSON.parse(await fs.readFile(projectPath, "utf-8")) as {
videoPath?: unknown;
editor?: { webcam?: { sourcePath?: unknown } };
};
const candidatePaths = [
rawProject.videoPath,
rawProject.editor?.webcam?.sourcePath,
];
try {
const rawProject = JSON.parse(await fs.readFile(projectPath, "utf-8")) as {
videoPath?: unknown;
editor?: { webcam?: { sourcePath?: unknown } };
};
const candidatePaths = [
rawProject.videoPath,
rawProject.editor?.webcam?.sourcePath,
];
for (const candidatePath of candidatePaths) {
if (typeof candidatePath !== "string" || candidatePath.trim().length === 0) {
continue;
}
for (const candidatePath of candidatePaths) {
if (
typeof candidatePath !== "string" ||
candidatePath.trim().length === 0
) {
continue;
}
const normalizedCandidatePath = normalizePath(
normalizeVideoSourcePath(candidatePath) ?? candidatePath,
);
protectedPaths.add(normalizedCandidatePath);
try {
protectedPaths.add(await fs.realpath(normalizedCandidatePath));
} catch {
// Ignore missing project media; project loading already surfaces that error.
}
const normalizedCandidatePath = normalizePath(
normalizeVideoSourcePath(candidatePath) ?? candidatePath,
);
protectedPaths.add(normalizedCandidatePath);
try {
protectedPaths.add(await fs.realpath(normalizedCandidatePath));
} catch {
// Ignore missing project media; project loading already surfaces that error.
}
} catch {
// Ignore malformed project files during retention pruning.
}
}),
);