1.0.14 Package Updates and Assets fix

This commit is contained in:
Allan Carr
2025-07-23 17:59:13 -07:00
parent d2bf897e2f
commit b23e446ed3
12 changed files with 216 additions and 57 deletions

View File

@@ -48,8 +48,21 @@ const thumbnailWorker = new Worker(
logger.debug(`[ThumbnailWorker] Completed thumbnail generation for ${file}`);
return result;
} catch (error) {
// Handle VipsJpeg and other thumbnail generation errors gracefully
const errorMessage = (error as Error).message;
if (
errorMessage.includes("VipsJpeg") ||
errorMessage.includes("Premature end") ||
errorMessage.includes("JPEG") ||
errorMessage.includes("SOS")
) {
logger.warning(`[ThumbnailWorker] Image processing error for ${file}, returning default icon:`, errorMessage);
return path.relative(path.dirname(file), AssetPaths.File);
}
logger.error(`[ThumbnailWorker] Error generating thumbnail for ${file}:`, error);
throw error;
// For other errors, still return default icon instead of throwing
return path.relative(path.dirname(file), AssetPaths.File);
}
},
{
@@ -126,23 +139,50 @@ async function processThumbnail(file: string, job?: Job): Promise<string> {
if (["application/pdf", "image/heic", "image/heif"].includes(type.mime)) {
logger.debug(`[ThumbnailWorker] Generating PDF/HEIC thumbnail for: ${file}`);
await generatePdfThumbnail(file, thumbPath);
try {
await generatePdfThumbnail(file, thumbPath);
} catch (pdfError) {
logger.warning(`[ThumbnailWorker] Failed to generate PDF/HEIC thumbnail for ${file}:`, {
error: pdfError,
message: (pdfError as Error).message
});
// Don't throw, just return the default file icon
return path.relative(path.dirname(file), AssetPaths.File);
}
} else if (type.mime.startsWith("video")) {
logger.debug(`[ThumbnailWorker] Generating video thumbnail for: ${file}`);
await simpleThumb(file, thumbPath, "250x?");
try {
await simpleThumb(file, thumbPath, "250x?");
} catch (videoError) {
logger.warning(`[ThumbnailWorker] Failed to generate video thumbnail for ${file}:`, {
error: videoError,
message: (videoError as Error).message
});
// Don't throw, just return the default file icon
return path.relative(path.dirname(file), AssetPaths.File);
}
} else {
logger.debug(`[ThumbnailWorker] Generating image thumbnail for: ${file}`);
const thumbnailBuffer = await imageThumbnail(file, {
responseType: "buffer",
height: 250,
width: 250
});
await fs.writeFile(thumbPath, thumbnailBuffer);
try {
const thumbnailBuffer = await imageThumbnail(file, {
responseType: "buffer",
height: 250,
width: 250
});
await fs.writeFile(thumbPath, thumbnailBuffer);
} catch (thumbnailError) {
logger.warning(`[ThumbnailWorker] Failed to generate image thumbnail for ${file}:`, {
error: thumbnailError,
message: (thumbnailError as Error).message
});
// Don't throw, just return the default file icon
return path.relative(path.dirname(file), AssetPaths.File);
}
}
return path.relative(path.dirname(file), thumbPath);
} catch (error) {
logger.error("[ThumbnailWorker] Error generating thumbnail:", {
logger.warning("[ThumbnailWorker] Could not generate thumbnail, returning default file icon:", {
thumbPath,
error,
message: (error as Error).message
@@ -189,7 +229,7 @@ export default async function GenerateThumbnail(file: string): Promise<string> {
logger.debug(`[GenerateThumbnail] Job completed for ${file}`);
return result as string;
} catch (error) {
logger.error(`[GenerateThumbnail] Job failed for ${file}:`, error);
logger.warning(`[GenerateThumbnail] Job failed for ${file}, returning default file icon:`, error);
return path.relative(path.dirname(file), AssetPaths.File);
}
}

View File

@@ -5,7 +5,6 @@ import { FileTypeResult } from "file-type/core";
import fs from "fs-extra";
import gm from "gm";
import path from "path";
import { fileURLToPath } from "url";
import { logger } from "../server.js";
import { generateUniqueHeicFilename } from "./generateUniqueFilename.js";
import { FolderPaths } from "./serverInit.js";
@@ -81,8 +80,12 @@ async function handleOriginalFile(fileInfo: { path: string; destination: string;
await fs.unlink(fileInfo.path);
}
} catch (error) {
logger.error("Error handling original file:", error);
throw error;
// Don't throw error for file handling issues - log as warning and continue
logger.warning("Error handling original HEIC file (continuing with conversion):", {
file: fileInfo.originalFilename,
path: fileInfo.path,
error: (error as Error).message
});
}
}
@@ -91,15 +94,38 @@ async function handleOriginalFile(fileInfo: { path: string; destination: string;
*/
async function convertToJpeg(inputPath: string, outputPath: string): Promise<string> {
return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(inputPath);
const writeStream = fs.createWriteStream(outputPath);
try {
const readStream = fs.createReadStream(inputPath);
const writeStream = fs.createWriteStream(outputPath);
gm(readStream)
.setFormat("jpg")
.stream()
.pipe(writeStream)
.on("finish", () => resolve(outputPath))
.on("error", reject);
// Add error handling for stream creation
readStream.on("error", (err) => {
logger.warning(`Error reading HEIC file ${inputPath}:`, err);
reject(new Error(`Failed to read HEIC file: ${err.message}`));
});
writeStream.on("error", (err) => {
logger.warning(`Error writing converted file ${outputPath}:`, err);
reject(new Error(`Failed to write converted file: ${err.message}`));
});
gm(readStream)
.setFormat("jpg")
.stream()
.on("error", (err) => {
logger.warning(`GraphicsMagick conversion error for ${inputPath}:`, err);
reject(new Error(`GraphicsMagick conversion failed: ${err.message}`));
})
.pipe(writeStream)
.on("finish", () => resolve(outputPath))
.on("error", (err) => {
logger.warning(`Stream pipe error for ${inputPath}:`, err);
reject(new Error(`Stream processing failed: ${err.message}`));
});
} catch (error) {
logger.warning(`Unexpected error in convertToJpeg for ${inputPath}:`, error);
reject(new Error(`Conversion setup failed: ${(error as Error).message}`));
}
});
}
@@ -127,33 +153,73 @@ export async function convertHeicFiles(files: Express.Multer.File[]) {
}));
// Add jobs and wait for completion of each before proceeding
const successfulConversions: Array<{ originalFilename: string; convertedFileName: string }> = [];
const failedConversions: Array<{ originalFilename: string; error: string }> = [];
for (const jobData of jobs) {
try {
const job = await heicQueue.add(jobData.name, jobData.data);
await job.waitUntilFinished(heicQueueEvents);
logger.debug(`Job ${job.id} finished successfully.`);
const result = await job.waitUntilFinished(heicQueueEvents);
if (result && result.success) {
logger.debug(`Job ${job.id} finished successfully.`);
successfulConversions.push({
originalFilename: jobData.data.fileInfo.originalFilename,
convertedFileName: jobData.data.convertedFileName
});
} else {
logger.warning(`Job ${job.id} completed but conversion failed for ${jobData.data.fileInfo.originalFilename}`);
failedConversions.push({
originalFilename: jobData.data.fileInfo.originalFilename,
error: result?.error || "Unknown conversion error"
});
}
} catch (error) {
logger.error(`Job for ${jobData.data.fileInfo.originalFilename} failed:`, error);
// Depending on your error handling strategy you might rethrow or continue
logger.warning(`Job for ${jobData.data.fileInfo.originalFilename} failed:`, error);
failedConversions.push({
originalFilename: jobData.data.fileInfo.originalFilename,
error: (error as Error).message
});
// Continue with next job instead of stopping
}
}
// Update original files list with new names, mimetype, and path
// Log summary
if (failedConversions.length > 0) {
logger.warning(
`HEIC conversion summary: ${successfulConversions.length} successful, ${failedConversions.length} failed:`,
{
failed: failedConversions.map((f) => f.originalFilename)
}
);
} else {
logger.debug(`HEIC conversion summary: All ${successfulConversions.length} files converted successfully`);
}
// Update original files list with new names, mimetype, and path - only for successful conversions
const filenameToIndex = new Map(files.map((f, i) => [f.filename, i]));
for (const { data } of jobs) {
const idx = filenameToIndex.get(data.fileInfo.originalFilename);
for (const conversion of successfulConversions) {
const idx = filenameToIndex.get(conversion.originalFilename);
if (idx !== undefined) {
const oldPath = files[idx].path;
files[idx].filename = data.convertedFileName;
files[idx].filename = conversion.convertedFileName;
files[idx].mimetype = "image/jpeg";
files[idx].path = path.join(data.fileInfo.destination, data.convertedFileName);
logger.debug(`Updated file entry: ${data.fileInfo.originalFilename} -> ${data.convertedFileName}`, {
files[idx].path = path.join(files[idx].destination, conversion.convertedFileName);
logger.debug(`Updated file entry: ${conversion.originalFilename} -> ${conversion.convertedFileName}`, {
oldPath,
newPath: files[idx].path,
newMimetype: files[idx].mimetype
});
}
}
// Remove failed conversions from the files array to prevent further processing
for (let i = files.length - 1; i >= 0; i--) {
if (failedConversions.some((f) => f.originalFilename === files[i].filename)) {
logger.debug(`Removing failed HEIC file from processing: ${files[i].filename}`);
files.splice(i, 1);
}
}
}
// Worker processing HEIC conversion jobs
@@ -166,11 +232,12 @@ const heicWorker = new Worker(
}>
) => {
const { fileInfo, convertedFileName } = job.data;
const outputPath = path.join(fileInfo.destination, convertedFileName);
try {
logger.debug(`Converting ${fileInfo.originalFilename} from HEIC to JPEG.`);
await job.updateProgress(10);
const outputPath = path.join(fileInfo.destination, convertedFileName);
await convertToJpeg(fileInfo.path, outputPath);
await job.updateProgress(50);
@@ -179,10 +246,60 @@ const heicWorker = new Worker(
logger.debug(`Successfully converted ${fileInfo.originalFilename} to JPEG.`);
await job.updateProgress(100);
return true;
return { success: true, convertedFileName };
} catch (error) {
logger.error(`Error converting ${fileInfo.originalFilename}:`, error);
throw error;
const errorMessage = (error as Error).message;
// Handle GraphicsMagick and other conversion errors gracefully
if (
errorMessage.includes("Magick") ||
errorMessage.includes("HEIC") ||
errorMessage.includes("corrupt") ||
errorMessage.includes("invalid")
) {
logger.warning(
`[heicWorker] HEIC conversion error for ${fileInfo.originalFilename}, moving to damaged folder:`,
errorMessage
);
// Clean up both the original HEIC file and any partially created JPEG
try {
// Move original HEIC file to damaged subfolder instead of deleting
const damagedDir = path.join(fileInfo.destination, FolderPaths.DamagedSubDir);
await fs.ensureDir(damagedDir);
const damagedFilePath = path.join(damagedDir, fileInfo.originalFilename);
if (await fs.pathExists(fileInfo.path)) {
await fs.move(fileInfo.path, damagedFilePath, { overwrite: true });
logger.debug(`Moved damaged HEIC file to: ${damagedFilePath}`);
}
} catch (cleanupError) {
logger.warning(`Failed to move damaged file for ${fileInfo.originalFilename}:`, cleanupError);
}
return { success: false, error: errorMessage, originalFilename: fileInfo.originalFilename };
}
logger.error(`[heicWorker] Unexpected error converting ${fileInfo.originalFilename}:`, error);
// For other errors, also clean up any partial files
try {
// Move original HEIC file to damaged subfolder instead of deleting
const damagedDir = path.join(fileInfo.destination, FolderPaths.DamagedSubDir);
await fs.ensureDir(damagedDir);
const damagedFilePath = path.join(damagedDir, fileInfo.originalFilename);
if (await fs.pathExists(fileInfo.path)) {
await fs.move(fileInfo.path, damagedFilePath, { overwrite: true });
logger.debug(`Moved damaged HEIC file after unexpected error to: ${damagedFilePath}`);
}
} catch (cleanupError) {
logger.warning(
`Failed to move damaged file after unexpected error for ${fileInfo.originalFilename}:`,
cleanupError
);
}
return { success: false, error: errorMessage, originalFilename: fileInfo.originalFilename };
}
},
{

View File

@@ -23,6 +23,7 @@ export const FolderPaths = {
ThumbsSubDir: "thumbs",
BillsSubDir: "bills",
ConvertedOriginalSubDir: "ConvertedOriginal",
DamagedSubDir: "DamagedOriginal",
StaticPath: "/static",
JobsFolder,
VendorsFolder

View File

@@ -19,7 +19,7 @@ export default function ValidateImsToken(req: Request, res: Response, next: Next
const token = req.headers.ims_token || req.headers["ims-token"] || req.headers["x-ims-token"];
if (token !== IMS_TOKEN) {
logger.warn("Invalid IMS token provided.", { provided: token });
logger.warning("Invalid IMS token provided.", { provided: token });
res.sendStatus(401);
return;
}