IO-1998 LMS Zip Download.
This commit is contained in:
77
jobs/jobsDownloadMedia.ts
Normal file
77
jobs/jobsDownloadMedia.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import fs from "fs-extra";
|
||||||
|
import multer from "multer";
|
||||||
|
import path from "path";
|
||||||
|
import { logger } from "../server";
|
||||||
|
import GenerateThumbnail from "../util/generateThumbnail";
|
||||||
|
import generateUniqueFilename from "../util/generateUniqueFilename";
|
||||||
|
import { ConvertHeicFiles } from "../util/heicConverter";
|
||||||
|
import { PathToRoFolder } from "../util/pathGenerators";
|
||||||
|
import { JobsListMedia } from "./jobsListMedia";
|
||||||
|
import JSZip from "jszip";
|
||||||
|
import ListableChecker from "../util/listableChecker";
|
||||||
|
import { JobRelativeFilePath } from "../util/serverInit";
|
||||||
|
|
||||||
|
//param: files: string[] | array of filenames.
|
||||||
|
export async function jobsDownloadMedia(req: Request, res: Response) {
|
||||||
|
const jobid: string = (req.body.jobid || "").trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Do we need all files or just some files?
|
||||||
|
const files: string[] = req.body.files || [];
|
||||||
|
const zip: JSZip = new JSZip();
|
||||||
|
await fs.ensureDir(PathToRoFolder(jobid));
|
||||||
|
|
||||||
|
logger.debug(`Generating batch download for Job ID ${jobid}`, files);
|
||||||
|
//Prepare the zip file.
|
||||||
|
|
||||||
|
const filesList: fs.Dirent[] = (
|
||||||
|
await fs.readdir(PathToRoFolder(jobid), {
|
||||||
|
withFileTypes: true,
|
||||||
|
})
|
||||||
|
).filter((f) => f.isFile() && ListableChecker(f));
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
//Get everything.
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
filesList.map(async (file) => {
|
||||||
|
//Do something async
|
||||||
|
const fileOnDisk: Buffer = await fs.readFile(
|
||||||
|
JobRelativeFilePath(jobid, file.name)
|
||||||
|
);
|
||||||
|
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
//Get the files that are in the list and see which are requested.
|
||||||
|
await Promise.all(
|
||||||
|
filesList.map(async (file) => {
|
||||||
|
if (files.includes(path.parse(path.basename(file.name)).base)) {
|
||||||
|
// File is in the set of requested files.
|
||||||
|
const fileOnDisk: Buffer = await fs.readFile(
|
||||||
|
JobRelativeFilePath(jobid, file.name)
|
||||||
|
);
|
||||||
|
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//Send it as a response to download it automatically.
|
||||||
|
// res.setHeader("Content-disposition", "attachment; filename=" + filename);
|
||||||
|
|
||||||
|
zip
|
||||||
|
.generateNodeStream({
|
||||||
|
type: "nodebuffer",
|
||||||
|
streamFiles: true,
|
||||||
|
//encodeFileName: (filename) => `${jobid}.zip`,
|
||||||
|
})
|
||||||
|
.pipe(res);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error downloading job media.", {
|
||||||
|
jobid,
|
||||||
|
error: (error as Error).message,
|
||||||
|
});
|
||||||
|
res.status(500).json((error as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import MediaFile from "../util/interfaces/MediaFile";
|
|||||||
import ListableChecker from "../util/listableChecker";
|
import ListableChecker from "../util/listableChecker";
|
||||||
import GenerateUrl from "../util/MediaUrlGen";
|
import GenerateUrl from "../util/MediaUrlGen";
|
||||||
import { PathToRoFolder } from "../util/pathGenerators";
|
import { PathToRoFolder } from "../util/pathGenerators";
|
||||||
import { FolderPaths } from "../util/serverInit";
|
import { FolderPaths, JobRelativeFilePath } from "../util/serverInit";
|
||||||
|
|
||||||
export async function JobsListMedia(req: Request, res: Response) {
|
export async function JobsListMedia(req: Request, res: Response) {
|
||||||
const jobid: string = (req.body.jobid || "").trim();
|
const jobid: string = (req.body.jobid || "").trim();
|
||||||
@@ -19,8 +19,13 @@ export async function JobsListMedia(req: Request, res: Response) {
|
|||||||
//We just uploaded files, we're going to send only those back.
|
//We just uploaded files, we're going to send only those back.
|
||||||
ret = await Promise.all(
|
ret = await Promise.all(
|
||||||
(req.files as Express.Multer.File[]).map(async (file) => {
|
(req.files as Express.Multer.File[]).map(async (file) => {
|
||||||
|
const relativeFilePath: string = JobRelativeFilePath(
|
||||||
|
jobid,
|
||||||
|
file.filename
|
||||||
|
);
|
||||||
|
|
||||||
const relativeThumbPath: string = await GenerateThumbnail(
|
const relativeThumbPath: string = await GenerateThumbnail(
|
||||||
path.join(FolderPaths.Jobs, jobid, file.filename)
|
relativeFilePath
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
src: GenerateUrl([
|
src: GenerateUrl([
|
||||||
@@ -38,6 +43,7 @@ export async function JobsListMedia(req: Request, res: Response) {
|
|||||||
thumbnailHeight: 250,
|
thumbnailHeight: 250,
|
||||||
thumbnailWidth: 250,
|
thumbnailWidth: 250,
|
||||||
filename: file.filename,
|
filename: file.filename,
|
||||||
|
relativeFilePath,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -46,15 +52,17 @@ export async function JobsListMedia(req: Request, res: Response) {
|
|||||||
await fs.readdir(PathToRoFolder(jobid), {
|
await fs.readdir(PathToRoFolder(jobid), {
|
||||||
withFileTypes: true,
|
withFileTypes: true,
|
||||||
})
|
})
|
||||||
).filter(
|
).filter((f) => f.isFile() && ListableChecker(f));
|
||||||
(f) =>
|
|
||||||
f.isFile() && !/(^|\/)\.[^\/\.]/g.test(f.name) && ListableChecker(f)
|
|
||||||
);
|
|
||||||
|
|
||||||
ret = await Promise.all(
|
ret = await Promise.all(
|
||||||
filesList.map(async (file) => {
|
filesList.map(async (file) => {
|
||||||
|
const relativeFilePath: string = JobRelativeFilePath(
|
||||||
|
jobid,
|
||||||
|
file.name
|
||||||
|
);
|
||||||
|
|
||||||
const relativeThumbPath: string = await GenerateThumbnail(
|
const relativeThumbPath: string = await GenerateThumbnail(
|
||||||
path.join(FolderPaths.Jobs, jobid, file.name)
|
relativeFilePath
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
src: GenerateUrl([
|
src: GenerateUrl([
|
||||||
@@ -72,6 +80,7 @@ export async function JobsListMedia(req: Request, res: Response) {
|
|||||||
thumbnailHeight: 250,
|
thumbnailHeight: 250,
|
||||||
thumbnailWidth: 250,
|
thumbnailWidth: 250,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
|
relativeFilePath,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"heic-convert": "^1.2.4",
|
"heic-convert": "^1.2.4",
|
||||||
"helmet": "^5.0.2",
|
"helmet": "^5.0.2",
|
||||||
"image-thumbnail": "^1.0.14",
|
"image-thumbnail": "^1.0.14",
|
||||||
|
"jszip": "^3.10.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.4",
|
"multer": "^1.4.4",
|
||||||
"response-time": "^2.3.2",
|
"response-time": "^2.3.2",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
BillsUploadMedia,
|
BillsUploadMedia,
|
||||||
} from "./bills/billsUploadMedia";
|
} from "./bills/billsUploadMedia";
|
||||||
import ValidateImsToken from "./util/validateToken";
|
import ValidateImsToken from "./util/validateToken";
|
||||||
|
import { jobsDownloadMedia } from "./jobs/jobsDownloadMedia";
|
||||||
|
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`),
|
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`),
|
||||||
@@ -129,6 +130,12 @@ app.post(
|
|||||||
JobRequestValidator,
|
JobRequestValidator,
|
||||||
jobsUploadMedia
|
jobsUploadMedia
|
||||||
);
|
);
|
||||||
|
app.post(
|
||||||
|
"/jobs/download",
|
||||||
|
ValidateImsToken,
|
||||||
|
JobRequestValidator,
|
||||||
|
jobsDownloadMedia
|
||||||
|
);
|
||||||
app.post(
|
app.post(
|
||||||
"/jobs/move", //JobRequestValidator,
|
"/jobs/move", //JobRequestValidator,
|
||||||
ValidateImsToken,
|
ValidateImsToken,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import fs from "fs-extra";
|
|||||||
|
|
||||||
function ListableChecker(file: fs.Dirent) {
|
function ListableChecker(file: fs.Dirent) {
|
||||||
if (file.name === "Thumbs.db") return false;
|
if (file.name === "Thumbs.db") return false;
|
||||||
return true;
|
if (!/(^|\/)\.[^\/\.]/g.test(file.name)) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ListableChecker;
|
export default ListableChecker;
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export const FolderPaths = {
|
|||||||
VendorsFolder,
|
VendorsFolder,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function JobRelativeFilePath(jobid: string, filename: string) {
|
||||||
|
return path.join(FolderPaths.Jobs, jobid, filename);
|
||||||
|
}
|
||||||
export default function InitServer() {
|
export default function InitServer() {
|
||||||
logger.info(`Ensuring Root media path exists: ${FolderPaths.Root}`);
|
logger.info(`Ensuring Root media path exists: ${FolderPaths.Root}`);
|
||||||
fs.ensureDirSync(FolderPaths.Root);
|
fs.ensureDirSync(FolderPaths.Root);
|
||||||
|
|||||||
34
yarn.lock
34
yarn.lock
@@ -1132,6 +1132,11 @@ image-thumbnail@^1.0.14:
|
|||||||
sharp "^0.28"
|
sharp "^0.28"
|
||||||
validator "^13.0.0"
|
validator "^13.0.0"
|
||||||
|
|
||||||
|
immediate@~3.0.5:
|
||||||
|
version "3.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||||
|
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
|
||||||
|
|
||||||
import-lazy@^2.1.0:
|
import-lazy@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz"
|
resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz"
|
||||||
@@ -1282,6 +1287,16 @@ jsonfile@^6.0.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
|
jszip@^3.10.0:
|
||||||
|
version "3.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.0.tgz#faf3db2b4b8515425e34effcdbb086750a346061"
|
||||||
|
integrity sha512-LDfVtOLtOxb9RXkYOwPyNBTQDL4eUbqahtoY6x07GiDJHwSYvn8sHHIw8wINImV3MqbMNve2gSuM1DDqEKk09Q==
|
||||||
|
dependencies:
|
||||||
|
lie "~3.3.0"
|
||||||
|
pako "~1.0.2"
|
||||||
|
readable-stream "~2.3.6"
|
||||||
|
setimmediate "^1.0.5"
|
||||||
|
|
||||||
keyv@^3.0.0:
|
keyv@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz"
|
resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz"
|
||||||
@@ -1306,6 +1321,13 @@ libheif-js@^1.10.0:
|
|||||||
resolved "https://registry.yarnpkg.com/libheif-js/-/libheif-js-1.12.0.tgz#9ad1ed16a8e6412b4d3d83565d285465a00e7305"
|
resolved "https://registry.yarnpkg.com/libheif-js/-/libheif-js-1.12.0.tgz#9ad1ed16a8e6412b4d3d83565d285465a00e7305"
|
||||||
integrity sha512-hDs6xQ7028VOwAFwEtM0Q+B2x2NW69Jb2MhQFUbk3rUrHzz4qo5mqS8VrqNgYnSc8TiUGnR691LnO4uIfEE23w==
|
integrity sha512-hDs6xQ7028VOwAFwEtM0Q+B2x2NW69Jb2MhQFUbk3rUrHzz4qo5mqS8VrqNgYnSc8TiUGnR691LnO4uIfEE23w==
|
||||||
|
|
||||||
|
lie@~3.3.0:
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
|
||||||
|
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
|
||||||
|
dependencies:
|
||||||
|
immediate "~3.0.5"
|
||||||
|
|
||||||
logform@^2.3.2, logform@^2.4.0:
|
logform@^2.3.2, logform@^2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz"
|
resolved "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz"
|
||||||
@@ -1598,6 +1620,11 @@ package-json@^6.3.0:
|
|||||||
registry-url "^5.0.0"
|
registry-url "^5.0.0"
|
||||||
semver "^6.2.0"
|
semver "^6.2.0"
|
||||||
|
|
||||||
|
pako@~1.0.2:
|
||||||
|
version "1.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||||
|
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||||
|
|
||||||
parse-cache-control@^1.0.1:
|
parse-cache-control@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz"
|
||||||
@@ -1744,7 +1771,7 @@ readable-stream@1.1.x:
|
|||||||
isarray "0.0.1"
|
isarray "0.0.1"
|
||||||
string_decoder "~0.10.x"
|
string_decoder "~0.10.x"
|
||||||
|
|
||||||
readable-stream@^2.0.6, readable-stream@^2.2.2:
|
readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@~2.3.6:
|
||||||
version "2.3.7"
|
version "2.3.7"
|
||||||
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
|
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
|
||||||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
||||||
@@ -1887,6 +1914,11 @@ set-blocking@~2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz"
|
||||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||||
|
|
||||||
|
setimmediate@^1.0.5:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||||
|
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
|
||||||
|
|
||||||
setprototypeof@1.2.0:
|
setprototypeof@1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz"
|
resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user