diff --git a/picture-gallery-server/package.json b/picture-gallery-server/package.json index 467375e..5eb8324 100644 --- a/picture-gallery-server/package.json +++ b/picture-gallery-server/package.json @@ -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" diff --git a/picture-gallery-server/src/app.ts b/picture-gallery-server/src/app.ts index d63bb1c..e92ae1d 100644 --- a/picture-gallery-server/src/app.ts +++ b/picture-gallery-server/src/app.ts @@ -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(walk(""))); +app.get("/directories", async (req, res) => { + res.json(await walk("")); }); // All other GET requests not handled before will return our React app diff --git a/picture-gallery-server/src/controller/images.ts b/picture-gallery-server/src/controller/images.ts index 1984dd6..7602f98 100644 --- a/picture-gallery-server/src/controller/images.ts +++ b/picture-gallery-server/src/controller/images.ts @@ -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( + 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({ + 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({ images })); } catch (e) { consoleLogger.warn(`Error when trying to access ${req.path}: ${e}`); diff --git a/picture-gallery-server/src/fsExtension.ts b/picture-gallery-server/src/fsExtension.ts index 9c5734a..98ebe2f 100644 --- a/picture-gallery-server/src/fsExtension.ts +++ b/picture-gallery-server/src/fsExtension.ts @@ -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 => + 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 => { + 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, }; }; diff --git a/picture-gallery-server/src/thumbnails.ts b/picture-gallery-server/src/thumbnails.ts index 7f6b6ff..687c07a 100644 --- a/picture-gallery-server/src/thumbnails.ts +++ b/picture-gallery-server/src/thumbnails.ts @@ -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}` + ); }); };