Skip to content

Commit

Permalink
refactor: Implement file streaming for asset route. Fixes #818
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedBassem committed Jan 5, 2025
1 parent 88dc6f9 commit c98722c
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 23 deletions.
75 changes: 52 additions & 23 deletions apps/web/app/api/assets/[assetId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { createContextFromRequest } from "@/server/api/client";
import { and, eq } from "drizzle-orm";

import { assets } from "@hoarder/db/schema";
import { readAsset } from "@hoarder/shared/assetdb";
import {
createAssetReadStream,
getAssetSize,
readAssetMetadata,
} from "@hoarder/shared/assetdb";

export const dynamic = "force-dynamic";
export async function GET(
Expand All @@ -22,35 +26,60 @@ export async function GET(
return Response.json({ error: "Asset not found" }, { status: 404 });
}

const { asset, metadata } = await readAsset({
userId: ctx.user.id,
assetId: params.assetId,
});
const [metadata, size] = await Promise.all([
readAssetMetadata({
userId: ctx.user.id,
assetId: params.assetId,
}),

getAssetSize({
userId: ctx.user.id,
assetId: params.assetId,
}),
]);

const range = request.headers.get("Range");
if (range) {
const parts = range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : asset.length - 1;

// TODO: Don't read the whole asset into memory in the first place
const chunk = asset.subarray(start, end + 1);
return new Response(chunk, {
status: 206, // Partial Content
headers: {
"Content-Range": `bytes ${start}-${end}/${asset.length}`,
"Accept-Ranges": "bytes",
"Content-Length": chunk.length.toString(),
"Content-type": metadata.contentType,
},
const end = parts[1] ? parseInt(parts[1], 10) : size - 1;

const stream = createAssetReadStream({
userId: ctx.user.id,
assetId: params.assetId,
start,
end,
});
} else {
return new Response(asset, {
status: 200,
headers: {
"Content-Length": asset.length.toString(),
"Content-type": metadata.contentType,

return new Response(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
stream as any,
{
status: 206, // Partial Content
headers: {
"Content-Range": `bytes ${start}-${end}/${size}`,
"Accept-Ranges": "bytes",
"Content-Length": (end - start + 1).toString(),
"Content-type": metadata.contentType,
},
},
);
} else {
const stream = createAssetReadStream({
userId: ctx.user.id,
assetId: params.assetId,
});

return new Response(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
stream as any,
{
status: 200,
headers: {
"Content-Length": size.toString(),
"Content-type": metadata.contentType,
},
},
);
}
}
19 changes: 19 additions & 0 deletions packages/shared/assetdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,25 @@ export async function readAsset({
return { asset, metadata };
}

export function createAssetReadStream({
userId,
assetId,
start,
end,
}: {
userId: string;
assetId: string;
start?: number;
end?: number;
}) {
const assetDir = getAssetDir(userId, assetId);

return fs.createReadStream(path.join(assetDir, "asset.bin"), {
start,
end,
});
}

export async function readAssetMetadata({
userId,
assetId,
Expand Down

0 comments on commit c98722c

Please sign in to comment.