mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-27 08:15:42 +00:00
fix(recordings): fail closed during prune project scans
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user