From c3609984b560e4c4160ca5dd349372a1cfd0cc17 Mon Sep 17 00:00:00 2001 From: Tilman Date: Wed, 3 Dec 2025 00:25:50 +0100 Subject: [PATCH] Stream zip archive in download-directory API Refactored the zip archive creation to stream output directly from the zip process, improving memory efficiency and response time. This fixes out-of-memory crashes on low-end devices. --- routes/api/files/download-directory.tsx | 54 +++++++++++++++++++------ 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/routes/api/files/download-directory.tsx b/routes/api/files/download-directory.tsx index ea72065..a5494e6 100644 --- a/routes/api/files/download-directory.tsx +++ b/routes/api/files/download-directory.tsx @@ -39,25 +39,55 @@ export const handler: Handlers = { const userRootPath = join(filesRootPath, context.state.user.id); const fullDirectoryPath = join(userRootPath, directoryPath); - // Use the zip command to create the archive + // Use the zip command to create the archive with streaming const zipProcess = new Deno.Command('zip', { args: ['-r', '-', '.'], cwd: fullDirectoryPath, stdout: 'piped', stderr: 'piped', + }).spawn(); + + // Get the zip stream from the process stdout + const zipStream = zipProcess.stdout; + + // Monitor process errors and log them (stream will end on error) + zipProcess.status.then(async (status) => { + if (status.code !== 0) { + console.error('Zip command failed with code:', status.code); + // Read and log stderr for error details + try { + const stderrReader = zipProcess.stderr.getReader(); + try { + const chunks: Uint8Array[] = []; + while (true) { + const { done, value } = await stderrReader.read(); + if (done) break; + if (value) chunks.push(value); + } + if (chunks.length > 0) { + // Concatenate chunks + const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0); + const combined = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + combined.set(chunk, offset); + offset += chunk.length; + } + const errorText = new TextDecoder().decode(combined); + console.error('Zip stderr:', errorText); + } + } finally { + stderrReader.releaseLock(); + } + } catch (error) { + console.error('Error reading stderr:', error); + } + } + }).catch((error) => { + console.error('Zip process error:', error); }); - const { code, stdout, stderr } = await zipProcess.output(); - - if (code !== 0) { - const errorText = new TextDecoder().decode(stderr); - - console.error('Zip command failed:', errorText); - - return new Response('Error creating zip archive', { status: 500 }); - } - - return new Response(stdout, { + return new Response(zipStream, { status: 200, headers: { 'content-type': 'application/zip',