import { Request, Response } from "express"; import { fileTypeFromFile } from "file-type"; import { FileTypeResult } from "file-type/core"; import fs from "fs-extra"; import { logger } from "../server.js"; import GenerateUrl from "../util/MediaUrlGen.js"; import GenerateThumbnail from "../util/generateThumbnail.js"; import MediaFile from "../util/interfaces/MediaFile.js"; import ListableChecker from "../util/listableChecker.js"; import { PathToRoFolder } from "../util/pathGenerators.js"; import { FolderPaths, JobRelativeFilePath } from "../util/serverInit.js"; export async function JobsListMedia(req: Request, res: Response) { const jobid: string = (req.body.jobid || "").trim(); await fs.ensureDir(PathToRoFolder(jobid)); logger.debug("Listing media for job: " + PathToRoFolder(jobid)); try { let ret: MediaFile[]; if (req.files) { ret = await processUploadedFiles(req.files as Express.Multer.File[], jobid); } else { ret = await processExistingFiles(jobid); } if (!res.headersSent) res.json(ret); } catch (error) { logger.error("Error listing job media.", { jobid, error }); if (!res.headersSent) res.status(500).json(error); } } async function processUploadedFiles(files: Express.Multer.File[], jobid: string): Promise { const processFile = async (file: Express.Multer.File): Promise => { const relativeFilePath: string = JobRelativeFilePath(jobid, file.filename); try { const relativeThumbPath: string = await GenerateThumbnail(relativeFilePath); const type: FileTypeResult | undefined = await Promise.race([ fileTypeFromFile(relativeFilePath), new Promise((resolve) => setTimeout(() => resolve(undefined), 5000)) ]); return { type, size: file.size, src: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, file.filename]), thumbnail: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, relativeThumbPath]), thumbnailHeight: 250, thumbnailWidth: 250, filename: file.filename, name: file.filename, path: relativeFilePath, thumbnailPath: relativeThumbPath }; } catch (error) { logger.error(`Error processing uploaded file ${file.filename}:`, error); return { type: undefined, size: file.size, src: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, file.filename]), thumbnail: GenerateUrl([FolderPaths.StaticPath, "assets", "file.svg"]), thumbnailHeight: 250, thumbnailWidth: 250, filename: file.filename, name: file.filename, path: relativeFilePath, thumbnailPath: "" }; } }; return (await Promise.all(files.map(processFile))).filter((r): r is MediaFile => r !== null); } async function processExistingFiles(jobid: string): Promise { const dirPath = PathToRoFolder(jobid); const mediaFiles: MediaFile[] = []; const dir = await fs.opendir(dirPath); for await (const dirent of dir) { if (!dirent.isFile() || !ListableChecker(dirent)) continue; const file = dirent.name; const relativeFilePath: string = JobRelativeFilePath(jobid, file); try { if (!(await fs.pathExists(relativeFilePath))) { logger.warning(`File no longer exists: ${relativeFilePath}`); continue; } const fileStats = await Promise.race([ fs.stat(relativeFilePath), new Promise((_, reject) => setTimeout(() => reject(new Error("File stat timeout")), 5000)) ]); const relativeThumbPath: string = await GenerateThumbnail(relativeFilePath); const type: FileTypeResult | undefined = await Promise.race([ fileTypeFromFile(relativeFilePath), new Promise((resolve) => setTimeout(() => resolve(undefined), 5000)) ]); mediaFiles.push({ type, size: fileStats.size, src: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, file]), thumbnail: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, relativeThumbPath]), thumbnailHeight: 250, thumbnailWidth: 250, filename: file, name: file, path: relativeFilePath, thumbnailPath: relativeThumbPath }); } catch (error) { logger.error(`Error processing existing file ${file}:`, error); try { const fileStats = await fs.stat(relativeFilePath); mediaFiles.push({ type: undefined, size: fileStats.size, src: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, file]), thumbnail: GenerateUrl([FolderPaths.StaticPath, "assets", "file.svg"]), thumbnailHeight: 250, thumbnailWidth: 250, filename: file, name: file, path: relativeFilePath, thumbnailPath: "" }); } catch (statError) { logger.error(`Could not get stats for ${file}:`, statError); } } } return mediaFiles; }