Better error handling

Remove `image-size` dependency as sharp covers the needed functionality.
This commit is contained in:
Stefan Forstenlechner 2022-04-25 23:31:17 +02:00
parent 6513cd7d76
commit a7c4a17ecc
5 changed files with 71 additions and 24 deletions

View File

@ -35,7 +35,6 @@
},
"dependencies": {
"express": "4.17.3",
"image-size": "1.0.1",
"sharp": "0.30.3",
"winston": "3.7.2",
"express-winston": "4.2.0"

View File

@ -1,6 +1,5 @@
import express from "express";
import * as path from "path";
import { a, Folders } from "./models";
import { walk } from "./fsExtension";
import { initThumbnailsAsync } from "./thumbnails";
import { publicPath } from "./paths";
@ -31,8 +30,8 @@ const imagesPath = "/images";
app.get(`${imagesPath}(/*)?`, getImages(imagesPath));
app.get("/directories", (req, res) => {
res.json(a<Folders>(walk("")));
app.get("/directories", async (req, res) => {
res.json(await walk(""));
});
// All other GET requests not handled before will return our React app

View File

@ -1,13 +1,20 @@
import fs from "fs";
import sizeOf from "image-size";
import express from "express";
import sharp from "sharp";
import { publicPath, thumbnailPath, thumbnailPublicPath } from "../paths";
import { a, Folder, Image } from "../models";
import { createThumbnailAsyncForImage } from "../thumbnails";
import { consoleLogger } from "../logging";
function notEmpty<TValue>(
value: TValue | void | null | undefined
): value is TValue {
return value !== null && value !== undefined;
}
export const getImages =
(imagesPath: string) => (req: express.Request, res: express.Response) => {
(imagesPath: string) =>
async (req: express.Request, res: express.Response) => {
const requestedPath = decodeURI(req.path.substring(imagesPath.length));
const normalizedPath = requestedPath === "/" ? "" : requestedPath;
@ -17,24 +24,32 @@ export const getImages =
});
const thumbnails = fs.readdirSync(thumbnailPublicPath + requestedPath);
const images: Image[] = dirents
const imagesToBeLoaded = dirents
.filter((f) => f.isFile())
.map((f) => {
const thumbnailExists: boolean = thumbnails.includes(f.name);
if (!thumbnailExists) {
createThumbnailAsyncForImage(`${requestedPath}/${f.name}`);
}
const dimensions = sizeOf(`${publicPath}${requestedPath}/${f.name}`);
const widthAndHeightSwap = dimensions.orientation > 4; // see https://exiftool.org/TagNames/EXIF.html
return {
src: thumbnailExists
? `/staticImages${thumbnailPath}${normalizedPath}/${f.name}`
: `/staticImages${normalizedPath}/${f.name}`,
width: widthAndHeightSwap ? dimensions.height : dimensions.width,
height: widthAndHeightSwap ? dimensions.width : dimensions.height,
};
return sharp(`${publicPath}${requestedPath}/${f.name}`)
.metadata()
.then((metadata) => {
const widthAndHeightSwap = metadata.orientation > 4; // see https://exiftool.org/TagNames/EXIF.html
return a<Image>({
src: thumbnailExists
? `/staticImages${thumbnailPath}${normalizedPath}/${f.name}`
: `/staticImages${normalizedPath}/${f.name}`,
width: widthAndHeightSwap ? metadata.height : metadata.width,
height: widthAndHeightSwap ? metadata.width : metadata.height,
});
})
.catch((err) => {
consoleLogger.error(
`Reading metadata from ${publicPath}${requestedPath}/${f.name} produced the following error: ${err.message}`
);
});
});
const images = (await Promise.all(imagesToBeLoaded)).filter(notEmpty);
res.json(a<Folder>({ images }));
} catch (e) {
consoleLogger.warn(`Error when trying to access ${req.path}: ${e}`);

View File

@ -1,20 +1,48 @@
import fs from "fs";
import * as path from "path";
import sharp from "sharp";
import { Folders } from "./models";
import { publicPath, thumbnailPath } from "./paths";
import { consoleLogger } from "./logging";
export const walk = (dirPath: string): Folders => {
const dirEnts = fs.readdirSync(`${publicPath}/${dirPath}`, {
const isImageProcessable = async (filePath: string): Promise<boolean> =>
sharp(filePath)
.metadata()
.then(() => true)
.catch((err) => {
consoleLogger.error(
`Reading metadata from ${filePath} produced the following error: ${err.message}`
);
return false;
});
export const walk = async (dirPath: string): Promise<Folders> => {
const dirEnts = fs.readdirSync(path.posix.join(publicPath, dirPath), {
withFileTypes: true,
});
dirEnts.filter((f) => f.isFile());
const numberOfFiles = (
await Promise.all(
dirEnts
.filter((f) => f.isFile())
.map((f) => path.posix.join(publicPath, dirPath, f.name))
.map(isImageProcessable)
)
).filter((a) => a).length;
const children = await Promise.all(
dirEnts
.filter((d) => d.isDirectory())
.filter((d) => !d.name.includes(thumbnailPath.substring(1)))
.map((d) => walk(path.posix.join(dirPath, d.name)))
);
return {
name: path.basename(dirPath) || "Home",
fullPath: dirPath,
numberOfFiles: dirEnts.filter((f) => f.isFile()).length,
children: dirEnts
.filter((d) => d.isDirectory())
.filter((d) => !d.name.includes(thumbnailPath.substring(1)))
.map((d) => walk(path.posix.join(dirPath, d.name))),
numberOfFiles,
children,
};
};

View File

@ -2,6 +2,7 @@ import sharp from "sharp";
import fs from "fs";
import path from "path";
import { publicPath, thumbnailPath, thumbnailPublicPath } from "./paths";
import { consoleLogger } from "./logging";
const percentage = 25;
const minimumPixelForThumbnail = 1024;
@ -29,6 +30,11 @@ export const createThumbnailAsyncForImage = (image: string) => {
.toFile(`${thumbnailPublicPath}${image}`);
}
);
})
.catch((err) => {
consoleLogger.error(
`Thumbnail creation of ${publicPath}${image} produced the following error: ${err.message}`
);
});
};