37 lines
1.5 KiB
TypeScript
37 lines
1.5 KiB
TypeScript
import { createReadStream, statSync } from "node:fs";
|
|||
|
|
import { Readable } from "node:stream";
|
||
|
|
import { finishDownloadJob, getDownloadJob, jsonError, requireDownloadAccess } from "../helpers/downloadJobs";
|
||
|
|
|
||
|
|
function contentDisposition(fileName: string) {
|
||
|
|
const ascii = fileName.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
|
||
|
|
return `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(fileName)}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function handle(request: Request) {
|
||
|
|
try {
|
||
|
|
const access = await requireDownloadAccess(request);
|
||
|
|
if (access.error) return access.error;
|
||
|
|
|
||
|
|
const jobId = new URL(request.url).searchParams.get("jobId") || "";
|
||
|
|
const job = getDownloadJob(jobId, access.userId);
|
||
|
|
if (!job || job.status !== "ready" || !job.filePath || !job.fileName) {
|
||
|
|
return jsonError("This download is not ready or has already been collected.", 404);
|
||
|
|
}
|
||
|
|
|
||
|
|
const size = statSync(job.filePath).size;
|
||
|
|
const stream = createReadStream(job.filePath);
|
||
|
|
stream.on("close", () => finishDownloadJob(job));
|
||
|
|
|
||
|
|
return new Response(Readable.toWeb(stream) as ReadableStream, {
|
||
|
|
headers: {
|
||
|
|
"Content-Type": job.kind === "playlist" ? "application/zip" : "audio/mpeg",
|
||
|
|
"Content-Length": String(size),
|
||
|
|
"Content-Disposition": contentDisposition(job.fileName),
|
||
|
|
"Cache-Control": "no-store",
|
||
|
|
},
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
return jsonError(error instanceof Error ? error.message : "Unable to send the file.", 500);
|
||
|
|
}
|
||
|
|
}
|