From 576b0e02f67d74ffafd6c5acb80d4d269744a072 Mon Sep 17 00:00:00 2001 From: Saud Fatayerji Date: Thu, 16 Nov 2023 02:24:10 +0300 Subject: [PATCH] Changed PdfFile.filename to exclude file extensions. Other naming fixes --- .../src/routes/api/operations-controller.ts | 40 +++++++------- .../src/routes/api/workflow-controller.ts | 46 +--------------- server-node/src/utils/endpoint-utils.ts | 55 +++++++++++++++++-- shared-operations/src/functions/impose.ts | 15 ++++- shared-operations/src/functions/mergePDFs.ts | 3 +- .../src/functions/rotatePages.ts | 7 ++- .../src/functions/scaleContent.ts | 4 +- shared-operations/src/functions/scalePage.ts | 4 +- shared-operations/src/functions/splitOn.ts | 4 +- .../src/functions/subDocumentFunctions.ts | 8 +-- shared-operations/src/wrappers/PdfFile.ts | 5 ++ 11 files changed, 108 insertions(+), 83 deletions(-) diff --git a/server-node/src/routes/api/operations-controller.ts b/server-node/src/routes/api/operations-controller.ts index b2450311..62edc484 100644 --- a/server-node/src/routes/api/operations-controller.ts +++ b/server-node/src/routes/api/operations-controller.ts @@ -1,13 +1,13 @@ import Operations from '../../utils/pdf-operations'; -import { respondWithPdfFile, response_mustHaveExactlyOneFile } from '../../utils/endpoint-utils'; +import { respondWithPdfFile, respondWithPdfFiles, response_mustHaveExactlyOneFile } from '../../utils/endpoint-utils'; import { PdfFile, PdfFileSchema } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile' import express, { Request, Response, RequestHandler } from 'express'; const router = express.Router(); import multer from 'multer'; const upload = multer(); -import Joi, { array } from 'joi'; +import Joi from 'joi'; function registerEndpoint(endpoint: string, nameToAppend: string, @@ -37,32 +37,34 @@ function registerEndpoint(endpoint: string, } const processed = await operationFunction(value) - if (Array.isArray(processed)) { - // TODO zip multiple files - } else { - processed.filename = appendToFilename(processed.filename, nameToAppend); + + if (body.files && Array.isArray(processed)) { // MIMO + respondWithPdfFiles(res, processed, nameToAppend); + } else if (body.file && Array.isArray(processed)) { // SIMO + respondWithPdfFiles(res, processed, body.file.filename + nameToAppend); + } else if (body.files && !Array.isArray(processed)) { // MISO + respondWithPdfFile(res, processed); + } else if (body.file && !Array.isArray(processed)) { // SISO respondWithPdfFile(res, processed); } }); } -/** - * appends a string before the last '.' of the given filename - */ -function appendToFilename(filename: string, str: string) { - return filename.replace(/(\.[^.]+)$/, str+'$1') -} - -registerEndpoint("/merge-pdfs", "_merged", upload.single("file"), Operations.mergePDFs, Joi.object({ +registerEndpoint("/merge-pdfs", "", upload.any(), Operations.mergePDFs, Joi.object({ files: Joi.array().items(PdfFileSchema).required(), -}).required()) +}).required()); -registerEndpoint("/rotate-pdf", "_rotated", upload.single("file"), Operations.rotatePages, Joi.object({ +registerEndpoint("/split-pdf", "_split", upload.single("file"), Operations.splitPDF, Joi.object({ + file: PdfFileSchema.required(), + pageNumbers: Joi.string().required(), +}).required()); + +registerEndpoint("/rotate-pdf", "", upload.single("file"), Operations.rotatePages, Joi.object({ file: PdfFileSchema.required(), rotation: Joi.alternatives().try(Joi.number(), Joi.array().items(Joi.number())).required(), -}).required()) +}).required()); -registerEndpoint("/update-metadata", "_edited-metadata", upload.single("file"), Operations.updateMetadata, Joi.object({ +registerEndpoint("/update-metadata", "", upload.single("file"), Operations.updateMetadata, Joi.object({ file: PdfFileSchema.required(), deleteAll: Joi.string(), author: Joi.string(), @@ -75,6 +77,6 @@ registerEndpoint("/update-metadata", "_edited-metadata", upload.single("file"), title: Joi.string(), trapped: Joi.string(), allRequestParams: Joi.object().pattern(Joi.string(), Joi.string()), -}).required()) +}).required()); export default router; \ No newline at end of file diff --git a/server-node/src/routes/api/workflow-controller.ts b/server-node/src/routes/api/workflow-controller.ts index 16320b8e..3188b952 100644 --- a/server-node/src/routes/api/workflow-controller.ts +++ b/server-node/src/routes/api/workflow-controller.ts @@ -1,13 +1,12 @@ import express, { Request, Response } from 'express'; import crypto from 'crypto'; -import stream from "stream"; -import Archiver from 'archiver'; import multer from 'multer' const upload = multer(); import Operations from "../../utils/pdf-operations"; import { traverseOperations } from "@stirling-pdf/shared-operations/src/workflow/traverseOperations"; import { PdfFile, RepresentationType } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile'; +import { respondWithPdfFiles } from '../../utils/endpoint-utils'; const activeWorkflows: any = {}; @@ -50,7 +49,7 @@ router.post("/:workflowUuid?", [ } console.log("Download"); - await downloadHandler(res, pdfResults); + await respondWithPdfFiles(res, pdfResults, "workflow-results"); } else { console.log("Start Aync Workflow"); @@ -164,7 +163,7 @@ router.get("/result/:workflowUuid", async (req: Request, res: Response) => { return } - await downloadHandler(res, workflow.result); + await respondWithPdfFiles(res, workflow.result, "workflow-results"); // Delete workflow / results when done. delete activeWorkflows[req.params.workflowUuid]; }); @@ -187,43 +186,4 @@ function generateWorkflowID() { return crypto.randomUUID(); } -async function downloadHandler(res: Response, pdfResults: PdfFile[]) { - if(pdfResults.length == 0) { - res.status(500).json({"warning": "The workflow had no outputs."}); - } - else if(pdfResults.length > 1) { - // TODO: Also allow the user to download multiple files without zip compressen, because this is kind of slow... - res.writeHead(200, { - 'Content-Type': 'application/zip', - 'Content-disposition': 'attachment; filename=workflow-results.zip' - }); - - var zip = Archiver('zip'); - - // Stream the file to the user. - zip.pipe(res); - - console.log("Adding Files to ZIP..."); - - for (let i = 0; i < pdfResults.length; i++) { - // TODO: Implement other file types (mostly fro image & text extraction) - // TODO: Check for name collisions - zip.append(Buffer.from(await pdfResults[i].uint8Array), { name: pdfResults[i].filename + ".pdf" }); - } - - zip.finalize(); - console.log("Sent"); - } - else { - const readStream = new stream.PassThrough(); - readStream.end(await pdfResults[0].uint8Array); - - // TODO: Implement other file types (mostly fro image & text extraction) - res.set("Content-disposition", 'attachment; filename=' + pdfResults[0].filename + ".pdf"); - res.set("Content-Type", "application/pdf"); - - readStream.pipe(res); - } -} - export default router; \ No newline at end of file diff --git a/server-node/src/utils/endpoint-utils.ts b/server-node/src/utils/endpoint-utils.ts index 7c02bf59..0d78f0a9 100644 --- a/server-node/src/utils/endpoint-utils.ts +++ b/server-node/src/utils/endpoint-utils.ts @@ -1,19 +1,64 @@ import { Response } from 'express'; import { PdfFile } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile' +import Archiver from 'archiver'; -export async function respondWithFile(res: Response, bytes: Uint8Array, name: string, mimeType: string): Promise { +export async function respondWithFile(res: Response, uint8Array: Uint8Array, filename: string, mimeType: string): Promise { res.writeHead(200, { 'Content-Type': mimeType, - 'Content-disposition': 'attachment;filename=' + name, - 'Content-Length': bytes.length + 'Content-disposition': `attachment; filename="${filename}"`, + 'Content-Length': uint8Array.length }); - res.end(bytes); + res.end(uint8Array); } export async function respondWithPdfFile(res: Response, file: PdfFile): Promise { const byteArray = await file.uint8Array; - respondWithFile(res, byteArray, file.filename, "application/pdf"); + respondWithFile(res, byteArray, file.filename+".pdf", "application/pdf"); +} + +export async function respondWithZip(res: Response, filename: string, files: {uint8Array: Uint8Array, filename: string}[]): Promise { + if (files.length == 0) { + res.status(500).json({"warning": "The workflow had no outputs."}); + return; + } + + console.log(filename) + res.writeHead(200, { + 'Content-Type': 'application/zip', + 'Content-disposition': `attachment; filename="${filename}.zip"`, + }); + + // TODO: Also allow changing the compression level + var zip = Archiver('zip'); + + // Stream the file to the user. + zip.pipe(res); + + console.log("Adding Files to ZIP..."); + + for (let i = 0; i < files.length; i++) { + zip.append(Buffer.from(files[i].uint8Array), { name: files[i].filename }); + } + + zip.finalize(); + console.log("Sent"); +} + +export async function respondWithPdfFiles(res: Response, pdfFiles: PdfFile|PdfFile[], filename: string) { + const pdfResults = Array.isArray(pdfFiles) ? pdfFiles : [pdfFiles]; + + if(pdfResults.length == 0) { + res.status(500).json({"warning": "The workflow had no outputs."}); + } + else if (pdfResults.length == 1) { + respondWithPdfFile(res, pdfResults[0]) + } + else { + const promises = pdfResults.map(async (pdf) => {return{uint8Array: await pdf.uint8Array, filename: pdf.filename + ".pdf"}}) + const files = await Promise.all(promises); + respondWithZip(res, filename, files); + } } export function response_mustHaveExactlyOneFile(res: Response): void { diff --git a/shared-operations/src/functions/impose.ts b/shared-operations/src/functions/impose.ts index 7c69cc54..00cddca8 100644 --- a/shared-operations/src/functions/impose.ts +++ b/shared-operations/src/functions/impose.ts @@ -12,7 +12,8 @@ export type ImposeParamsBaseType = { pdfcpuWrapper: any; } export async function impose(params: ImposeParamsBaseType): Promise { - const result = new PdfFile(params.file.originalFilename, await params.pdfcpuWrapper.oneToOne([ + const uint8Array = await params.pdfcpuWrapper.oneToOne( + [ "pdfcpu.wasm", "nup", "-c", @@ -21,7 +22,17 @@ export async function impose(params: ImposeParamsBaseType): Promise { "/output.pdf", String(params.nup), "input.pdf", - ], await params.file.uint8Array), RepresentationType.Uint8Array, params.file.filename + "_imposed"); + ], + await params.file.uint8Array + ); + + const result = new PdfFile( + params.file.originalFilename, + uint8Array, + RepresentationType.Uint8Array, + params.file.filename + "_imposed" + ); + console.log("ImposeResult: ", result); return result; } \ No newline at end of file diff --git a/shared-operations/src/functions/mergePDFs.ts b/shared-operations/src/functions/mergePDFs.ts index 682ac977..ebde95b5 100644 --- a/shared-operations/src/functions/mergePDFs.ts +++ b/shared-operations/src/functions/mergePDFs.ts @@ -15,5 +15,6 @@ export async function mergePDFs(params: MergeParamsType): Promise { copiedPages.forEach((page) => mergedPdf.addPage(page)); } - return new PdfFile("mergedPDF", mergedPdf, RepresentationType.PDFLibDocument); + const newName = "("+params.files.map(input => input.filename).join("_and_") + ")_merged" + return new PdfFile("mergedPDF", mergedPdf, RepresentationType.PDFLibDocument, newName); }; \ No newline at end of file diff --git a/shared-operations/src/functions/rotatePages.ts b/shared-operations/src/functions/rotatePages.ts index 1c21392a..d78f7307 100644 --- a/shared-operations/src/functions/rotatePages.ts +++ b/shared-operations/src/functions/rotatePages.ts @@ -1,6 +1,6 @@ import { degrees } from 'pdf-lib'; -import { PdfFile } from '../wrappers/PdfFile'; +import { PdfFile, RepresentationType } from '../wrappers/PdfFile'; export type RotateParamsType = { file: PdfFile; @@ -10,7 +10,8 @@ export type RotateParamsType = { export async function rotatePages(params: RotateParamsType): Promise { const { file, rotation } = params; - const pages = (await file.pdfLibDocument).getPages(); + const pdfDoc = await file.pdfLibDocument; + const pages = pdfDoc.getPages(); if (Array.isArray(rotation)) { if (rotation.length != pages.length) { @@ -28,5 +29,5 @@ export async function rotatePages(params: RotateParamsType): Promise { }); } - return file; + return new PdfFile(file.originalFilename, pdfDoc, RepresentationType.PDFLibDocument, file.filename+"_rotated"); }; \ No newline at end of file diff --git a/shared-operations/src/functions/scaleContent.ts b/shared-operations/src/functions/scaleContent.ts index bed9b072..e28c8a27 100644 --- a/shared-operations/src/functions/scaleContent.ts +++ b/shared-operations/src/functions/scaleContent.ts @@ -1,6 +1,6 @@ import { PDFPage } from 'pdf-lib'; -import { PdfFile } from '../wrappers/PdfFile'; +import { PdfFile, RepresentationType } from '../wrappers/PdfFile'; export type ScaleContentParamsType = { file: PdfFile; @@ -24,7 +24,7 @@ export async function scaleContent(params: ScaleContentParamsType): Promise scalePage(page, scaleFactor)); } - return file; + return new PdfFile(file.originalFilename, pdfDoc, RepresentationType.PDFLibDocument, file.filename+"_scaledContent"); }; function scalePage(page: PDFPage, scaleFactor: number) { diff --git a/shared-operations/src/functions/scalePage.ts b/shared-operations/src/functions/scalePage.ts index 8df46ecf..42e58bd8 100644 --- a/shared-operations/src/functions/scalePage.ts +++ b/shared-operations/src/functions/scalePage.ts @@ -1,6 +1,6 @@ import { PDFPage } from 'pdf-lib'; -import { PdfFile } from '../wrappers/PdfFile'; +import { PdfFile, RepresentationType } from '../wrappers/PdfFile'; export type ScalePageParamsType = { file: PdfFile; @@ -24,7 +24,7 @@ export async function scalePage(params: ScalePageParamsType): Promise { pages.forEach(page => resize(page, pageSize)); } - return file; + return new PdfFile(file.originalFilename, pdfDoc, RepresentationType.PDFLibDocument, file.filename+"_scaledPages"); }; function resize(page: PDFPage, newSize: {width?:number,height?:number}) { diff --git a/shared-operations/src/functions/splitOn.ts b/shared-operations/src/functions/splitOn.ts index e300911f..1fa5f7b8 100644 --- a/shared-operations/src/functions/splitOn.ts +++ b/shared-operations/src/functions/splitOn.ts @@ -41,7 +41,7 @@ export async function splitOn(params: SplitOnParamsType) { console.log("File: ", file); // Remove detected Pages & Split - const pdfDoc = await file.pdfLibDocument; + const pdfDoc = await file.pdflibDocument; const numberOfPages = pdfDoc.getPageCount(); let pagesArray: number[] = []; @@ -71,7 +71,7 @@ export async function splitOn(params: SplitOnParamsType) { async function getPagesWithQRCode(file: PdfFile) { console.log("FileInQRPrev: ", file); - const pdfDoc = await file.pdfJsDocument; + const pdfDoc = await file.pdfjsDocument; console.log("FileInQRAfter: ", file); const pagesWithQR: number[] = []; diff --git a/shared-operations/src/functions/subDocumentFunctions.ts b/shared-operations/src/functions/subDocumentFunctions.ts index a00c28d1..ed927947 100644 --- a/shared-operations/src/functions/subDocumentFunctions.ts +++ b/shared-operations/src/functions/subDocumentFunctions.ts @@ -21,7 +21,7 @@ export async function sortPagesWithPreset(params: SortPagesWithPresetParamsType) throw new Error("Operation not supported"); } - const pdflibDocument = await file.pdfLibDocument; + const pdflibDocument = await file.pdflibDocument; const pageCount = pdflibDocument.getPageCount(); const sortIndecies = sortFunction(pageCount); @@ -36,7 +36,7 @@ export type RearrangePagesParamsType = { export async function rearrangePages(params: RearrangePagesParamsType): Promise { const { file, fancyPageSelector } = params; - const pdflibDocument = await file.pdfLibDocument; + const pdflibDocument = await file.pdflibDocument; const pagesToExtractArray = parseFancyPageSelector(fancyPageSelector, pdflibDocument.getPageCount()); const newDocument = selectPages({file: file, pagesToExtractArray}); @@ -50,7 +50,7 @@ export type SelectPagesParamsType = { export async function selectPages(params: SelectPagesParamsType): Promise { const { file, pagesToExtractArray } = params; - const pdflibDocument = await file.pdfLibDocument; + const pdflibDocument = await file.pdflibDocument; const subDocument = await PDFDocument.create(); @@ -75,7 +75,7 @@ export type RemovePagesParamsType = { export async function removePages(params: RemovePagesParamsType): Promise { const { file, pagesToRemoveArray } = params; - const pdflibDocument = await file.pdfLibDocument; + const pdflibDocument = await file.pdflibDocument; const pagesToExtractArray = invertSelection(pagesToRemoveArray, pdflibDocument.getPageIndices()) return selectPages({file: file, pagesToExtractArray}); diff --git a/shared-operations/src/wrappers/PdfFile.ts b/shared-operations/src/wrappers/PdfFile.ts index d85910ee..f8a2bd50 100644 --- a/shared-operations/src/wrappers/PdfFile.ts +++ b/shared-operations/src/wrappers/PdfFile.ts @@ -86,8 +86,13 @@ export class PdfFile { } constructor(originalFilename: string, representation: Uint8Array | PDFLibDocument | PDFJSDocument, representationType: RepresentationType, filename?: string) { + if (originalFilename.toLowerCase().endsWith(".pdf")) + originalFilename = originalFilename.slice(0, -4); this.originalFilename = originalFilename; + this.filename = filename ? filename : originalFilename; + if (this.filename.toLowerCase().endsWith(".pdf")) + this.filename = this.filename.slice(0, -4); this.representation = representation; this.representationType = representationType;