From ab275b5de72c8f607585c71dec563cf99a246936 Mon Sep 17 00:00:00 2001 From: PiratePeep <120330222+PiratePeep@users.noreply.github.com> Date: Sun, 23 Mar 2025 16:45:11 -0600 Subject: [PATCH] feat: add zip downloading --- Dockerfile | 3 +- public/results.js | 27 ++++++++++++++++++ src/components/base.tsx | 3 ++ src/helpers/zip.ts | 63 +++++++++++++++++++++++++++++++++++++++++ src/index.tsx | 40 +++++++++++++++++++------- 5 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 src/helpers/zip.ts diff --git a/Dockerfile b/Dockerfile index 6d8225a..f720809 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,7 +53,8 @@ RUN apk --no-cache add \ poppler-utils \ gcompat \ libva-utils \ - py3-numpy + py3-numpy \ + zip RUN apk --no-cache add calibre --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/ diff --git a/public/results.js b/public/results.js index 7659edb..a3b6db3 100644 --- a/public/results.js +++ b/public/results.js @@ -13,6 +13,33 @@ window.downloadAll = function () { }, index * 100); }); }; + +window.downloadZip = function () { + const jobId = window.location.pathname.split("/").pop(); + const userId = document.querySelector("meta[name='user-id']").content; + + fetch(`${webroot}/zip/${userId}/${jobId}`, { + method: "GET", + }) + .then((res) => { + if (res.ok) { + return res.blob(); + } + + return Promise.reject(new Error("Failed to download zip")); + }) + .then((blob) => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `convertx-${userId}-${jobId}.zip`; + document.body.appendChild(a); + a.click(); + a.remove(); + }) + .catch((err) => console.log(err)); +}; + const jobId = window.location.pathname.split("/").pop(); const main = document.querySelector("main"); let progressElem = document.querySelector("progress"); diff --git a/src/components/base.tsx b/src/components/base.tsx index 9f3db2e..748c4a1 100644 --- a/src/components/base.tsx +++ b/src/components/base.tsx @@ -4,16 +4,19 @@ export const BaseHtml = ({ children, title = "ConvertX", webroot = "", + userId = "", }: { children: JSX.Element; title?: string; webroot?: string; + userId?: string; }) => ( + {title} { + return new Promise((resolve, reject) => { + const absoluteInputPath = path.resolve(inputPath); + const absoluteOutputPath = path.resolve(outputPath); + + const allTargets = getAllTargets(); + const supportedExtensions = new Set(); + + Object.values(allTargets).forEach((extensions) => { + extensions.forEach((ext) => supportedExtensions.add(ext)); + }); + + fs.readdir(absoluteInputPath, { withFileTypes: true }, (err, entries) => { + if (err) { + return reject(err); + } + + const files = entries + .filter((entry) => entry.isFile()) + .filter((entry) => entry.name !== path.basename(outputPath)) + .map((entry) => entry.name) + .filter((filename) => { + // We don't want to zip files with unsafe characters + if (!/^[a-zA-Z0-9._-]+\.[a-zA-Z0-9]+$/.test(filename)) { + return false; + } + + const extension = path.extname(filename).substring(1).toLowerCase(); + return supportedExtensions.has(extension); + }); + + if (files.length === 0) { + return reject(new Error("No files to zip")); + } + + execFile( + "zip", + ["-j", absoluteOutputPath, ...files], + { cwd: absoluteInputPath }, + (error, stdout, stderr) => { + if (error) { + reject(error); + } + + if (stdout) { + console.log(`stdout: ${stdout}`); + } + + if (stderr) { + console.error(`stderr: ${stderr}`); + } + + resolve("Done"); + }, + ); + }); + }); +}; diff --git a/src/index.tsx b/src/index.tsx index 1041320..fb9a408 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -20,6 +20,7 @@ import { normalizeOutputFiletype, } from "./helpers/normalizeFiletype"; import "./helpers/printVersions"; +import { zip } from "./helpers/zip"; mkdir("./data", { recursive: true }).catch(console.error); const db = new Database("./data/mydb.sqlite", { create: true }); @@ -563,7 +564,7 @@ const app = new Elysia({ console.log("jobId set to:", id); return ( - + <>
job.num_files > 0); return ( - + <>
+ <>

Results

-
+
+ <>
{ - // TODO: Implement zip download if (!auth?.value) { return redirect(`${WEBROOT}/login`, 302); } @@ -1517,16 +1527,24 @@ const app = new Elysia({ return redirect(`${WEBROOT}/results`, 302); } - // const userId = decodeURIComponent(params.userId); - // const jobId = decodeURIComponent(params.jobId); - // const outputPath = `${outputDir}${userId}/`{jobId}/); + const userId = decodeURIComponent(params.userId); + const jobId = decodeURIComponent(params.jobId); + const outputPath = `${outputDir}${userId}/${jobId}/`; + const zipPath = `${outputPath}/convertx-${userId}-${jobId}.zip`; - // return Bun.zip(outputPath); + await zip(outputPath, zipPath); + + return Bun.file(zipPath); }, ) - .onError(({ error }) => { + .onError(({ error, set }) => { // log.error(` ${request.method} ${request.url}`, code, error); console.error(error); + + set.status = 500; + return { + message: "Internal Server Error", + }; }); if (process.env.NODE_ENV !== "production") {