Skip to content

Commit 56da89a

Browse files
committed
fix: recover completed browser uploads
1 parent f3799c6 commit 56da89a

2 files changed

Lines changed: 40 additions & 6 deletions

File tree

apps/web/actions/video/trigger-processing.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,32 @@
33
import { db } from "@cap/database";
44
import { getCurrentUser } from "@cap/database/auth/session";
55
import { videos } from "@cap/database/schema";
6+
import { Storage } from "@cap/web-backend";
67
import type { Video } from "@cap/web-domain";
78
import { eq } from "drizzle-orm";
9+
import { Effect, Schedule } from "effect";
10+
import { runPromise } from "@/lib/server";
811
import { startVideoProcessingWorkflow } from "@/lib/video-processing";
12+
import { decodeStorageVideo } from "@/lib/video-storage";
13+
14+
async function verifyRawFileUploaded(
15+
video: typeof videos.$inferSelect,
16+
rawFileKey: string,
17+
) {
18+
const [bucket] = await Storage.getAccessForVideo(
19+
decodeStorageVideo(video),
20+
).pipe(runPromise);
21+
const head = await bucket
22+
.headObject(rawFileKey)
23+
.pipe(
24+
Effect.retry({ times: 3, schedule: Schedule.exponential("100 millis") }),
25+
runPromise,
26+
);
27+
28+
if ((head.ContentLength ?? 0) <= 0) {
29+
throw new Error("Uploaded video file is empty");
30+
}
31+
}
932

1033
export async function triggerVideoProcessing({
1134
videoId,
@@ -27,6 +50,8 @@ export async function triggerVideoProcessing({
2750
if (!video) throw new Error("Video not found");
2851
if (video.ownerId !== user.id) throw new Error("Unauthorized");
2952

53+
await verifyRawFileUploaded(video, rawFileKey);
54+
3055
await startVideoProcessingWorkflow({
3156
videoId,
3257
userId: user.id,

apps/web/app/(org)/dashboard/import/file/ImportFilePage.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,10 @@ async function uploadVideoForServerProcessing(
286286
};
287287

288288
const getTotal = () => uploadState.total;
289+
const didFinishSending = () =>
290+
uploadState.total > 0 && uploadState.uploaded >= uploadState.total;
289291

290-
return { scheduleProgressUpdate, cleanup, getTotal };
292+
return { scheduleProgressUpdate, cleanup, getTotal, didFinishSending };
291293
};
292294

293295
const progressTracker = createProgressTracker();
@@ -309,13 +311,20 @@ async function uploadVideoForServerProcessing(
309311
progressTracker.scheduleProgressUpdate(loaded, total);
310312
},
311313
});
312-
progressTracker.cleanup();
313-
const total = progressTracker.getTotal() || 1;
314-
await sendProgressUpdate(uploadId, total, total);
315314
} catch (uploadError) {
316-
progressTracker.cleanup();
317-
throw uploadError;
315+
if (!progressTracker.didFinishSending()) {
316+
progressTracker.cleanup();
317+
throw uploadError;
318+
}
319+
320+
console.warn(
321+
"Upload request failed after all bytes were sent; verifying object before processing:",
322+
uploadError,
323+
);
318324
}
325+
progressTracker.cleanup();
326+
const total = progressTracker.getTotal() || file.size || 1;
327+
await sendProgressUpdate(uploadId, total, total);
319328

320329
setUploadStatus({
321330
status: "serverProcessing",

0 commit comments

Comments
 (0)