diff --git a/utils/uncompression.utils.ts b/utils/uncompression.utils.ts index 9f43c80..ecc557a 100644 --- a/utils/uncompression.utils.ts +++ b/utils/uncompression.utils.ts @@ -32,7 +32,7 @@ SOFTWARE. */ import { createReadStream, createWriteStream, existsSync, statSync } from "fs"; -import { isEmpty, isNil, isUndefined, remove, each, map } from "lodash"; +import { isEmpty, isNil, isUndefined, remove, each, map, reject } from "lodash"; import * as p7zip from "p7zip-threetwo"; import path from "path"; import sharp from "sharp"; @@ -65,239 +65,269 @@ const UNRAR_BIN_PATH = process.env.UNRAR_BIN_PATH || "/usr/local/bin/unrar"; export const extractComicInfoXMLFromRar = async ( filePath: string ): Promise => { - const result = { - filePath, - }; + try { + const result = { + filePath, + }; - // Create the target directory - const directoryOptions = { - mode: 0o2775, - }; - const { fileNameWithoutExtension, extension } = - getFileConstituents(filePath); - const targetDirectory = `${USERDATA_DIRECTORY}/covers/${fileNameWithoutExtension}`; - await createDirectory(directoryOptions, targetDirectory); + // Create the target directory + const directoryOptions = { + mode: 0o2775, + }; + const { fileNameWithoutExtension, extension } = + getFileConstituents(filePath); + const targetDirectory = `${USERDATA_DIRECTORY}/covers/${fileNameWithoutExtension}`; + await createDirectory(directoryOptions, targetDirectory); - const archive = new Unrar({ - path: path.resolve(filePath), - bin: `${UNRAR_BIN_PATH}`, // this will change depending on Docker base OS - }); - const filesInArchive: [RarFile] = await new Promise((resolve, reject) => { - return archive.list((err, entries) => { - resolve(entries); + const archive = new Unrar({ + path: path.resolve(filePath), + bin: `${UNRAR_BIN_PATH}`, // this will change depending on Docker base OS }); - }); - remove(filesInArchive, ({ type }) => type === "Directory"); - const comicInfoXML = remove( - filesInArchive, - ({ name }) => path.basename(name).toLowerCase() === "comicinfo.xml" - ); - remove( - filesInArchive, - ({ name }) => !IMPORT_IMAGE_FILE_FORMATS.includes(path.extname(name)) - ); - const files = filesInArchive.sort((a, b) => { - if (!isUndefined(a) && !isUndefined(b)) { - return path - .basename(a.name) - .toLowerCase() - .localeCompare(path.basename(b.name).toLowerCase()); - } - }); - const comicInfoXMLFilePromise = new Promise((resolve, reject) => { - let comicinfostring = ""; - if (!isUndefined(comicInfoXML[0])) { - console.log(path.basename(comicInfoXML[0].name)); - const comicInfoXMLFileName = path.basename(comicInfoXML[0].name); - const writeStream = createWriteStream( - `${targetDirectory}/${comicInfoXMLFileName}` - ); + const filesInArchive: [RarFile] = await new Promise( + (resolve, reject) => { + return archive.list((err, entries) => { + if(err) { + reject(err); + } + resolve(entries); + }); + } + ); - archive.stream(comicInfoXML[0]["name"]).pipe(writeStream); - writeStream.on("finish", async () => { - const readStream = createReadStream( + remove(filesInArchive, ({ type }) => type === "Directory"); + const comicInfoXML = remove( + filesInArchive, + ({ name }) => path.basename(name).toLowerCase() === "comicinfo.xml" + ); + + remove( + filesInArchive, + ({ name }) => !IMPORT_IMAGE_FILE_FORMATS.includes(path.extname(name).toLowerCase()) + ); + const files = filesInArchive.sort((a, b) => { + if (!isUndefined(a) && !isUndefined(b)) { + return path + .basename(a.name) + .toLowerCase() + .localeCompare(path.basename(b.name).toLowerCase()); + } + }); + const comicInfoXMLFilePromise = new Promise((resolve, reject) => { + let comicinfostring = ""; + if (!isUndefined(comicInfoXML[0])) { + console.log(path.basename(comicInfoXML[0].name)); + const comicInfoXMLFileName = path.basename( + comicInfoXML[0].name + ); + const writeStream = createWriteStream( `${targetDirectory}/${comicInfoXMLFileName}` ); - readStream.on("data", (data) => { - comicinfostring += data; + + archive.stream(comicInfoXML[0]["name"]).pipe(writeStream); + writeStream.on("finish", async () => { + const readStream = createReadStream( + `${targetDirectory}/${comicInfoXMLFileName}` + ); + readStream.on("data", (data) => { + comicinfostring += data; + }); + readStream.on("error", (error) => reject(error)); + readStream.on("end", async () => { + if ( + existsSync( + `${targetDirectory}/${comicInfoXMLFileName}` + ) + ) { + const comicInfoJSON = await convertXMLToJSON( + comicinfostring.toString() + ); + + resolve({ comicInfoJSON: comicInfoJSON.comicinfo }); + } + }); }); - readStream.on("error", (error) => reject(error)); - readStream.on("end", async () => { - if ( - existsSync(`${targetDirectory}/${comicInfoXMLFileName}`) - ) { - const comicInfoJSON = await convertXMLToJSON( - comicinfostring.toString() - ); - - resolve({ comicInfoJSON: comicInfoJSON.comicinfo }); - } - }); - }); - } else { - resolve({ comicInfoJSON: null }); - } - }); - - const coverFilePromise = new Promise((resolve, reject) => { - const coverFile = path.basename(files[0].name); - const sharpStream = sharp().resize(275).toFormat("png"); - const coverExtractionStream = archive.stream(files[0].name); - const resizeStream = coverExtractionStream.pipe(sharpStream); - - resizeStream.toFile(`${targetDirectory}/${coverFile}`, (err, info) => { - if (err) { - reject(err); + } else { + resolve({ comicInfoJSON: null }); } - checkFileExists(`${targetDirectory}/${coverFile}`).then((bool) => { - console.log(`${coverFile} exists: ${bool}`); - // orchestrate result - resolve({ - filePath, - name: fileNameWithoutExtension, - extension, - containedIn: targetDirectory, - fileSize: fse.statSync(filePath).size, - cover: { - filePath: path.relative( - process.cwd(), - `${targetDirectory}/${coverFile}` - ), - }, - }); - }); }); - }); - return Promise.all([comicInfoXMLFilePromise, coverFilePromise]); + const coverFilePromise = new Promise((resolve, reject) => { + const coverFile = path.basename(files[0].name); + const sharpStream = sharp().resize(275).toFormat("png"); + const coverExtractionStream = archive.stream(files[0].name); + const resizeStream = coverExtractionStream.pipe(sharpStream); + + resizeStream.toFile( + `${targetDirectory}/${coverFile}`, + (err, info) => { + if (err) { + reject(err); + } + checkFileExists(`${targetDirectory}/${coverFile}`).then( + (bool) => { + console.log(`${coverFile} exists: ${bool}`); + // orchestrate result + resolve({ + filePath, + name: fileNameWithoutExtension, + extension, + containedIn: targetDirectory, + fileSize: fse.statSync(filePath).size, + cover: { + filePath: path.relative( + process.cwd(), + `${targetDirectory}/${coverFile}` + ), + }, + }); + } + ); + } + ); + }); + + return Promise.all([comicInfoXMLFilePromise, coverFilePromise]); + } catch (err) { + reject(err); + } }; export const extractComicInfoXMLFromZip = async ( filePath: string ): Promise => { - // Create the target directory - const directoryOptions = { - mode: 0o2775, - }; - const { fileNameWithoutExtension, extension } = - getFileConstituents(filePath); - const targetDirectory = `${USERDATA_DIRECTORY}/covers/${fileNameWithoutExtension}`; - await createDirectory(directoryOptions, targetDirectory); + try { + // Create the target directory + const directoryOptions = { + mode: 0o2775, + }; + const { fileNameWithoutExtension, extension } = + getFileConstituents(filePath); + const targetDirectory = `${USERDATA_DIRECTORY}/covers/${fileNameWithoutExtension}`; + await createDirectory(directoryOptions, targetDirectory); - let filesToWriteToDisk = { coverFile: null, comicInfoXML: null }; - const extractionTargets = []; + let filesToWriteToDisk = { coverFile: null, comicInfoXML: null }; + const extractionTargets = []; - // read the archive - let filesFromArchive = await p7zip.read(path.resolve(filePath)); + // read the archive + let filesFromArchive = await p7zip.read(path.resolve(filePath)); - // only allow allowed image formats - remove( - filesFromArchive.files, - ({ name }) => !IMPORT_IMAGE_FILE_FORMATS.includes(path.extname(name)) - ); - - // detect ComicInfo.xml - const comicInfoXMLFileObject = remove( - filesFromArchive.files, - (file) => path.basename(file.name.toLowerCase()) === "comicinfo.xml" - ); - - // Natural sort - const files = filesFromArchive.files.sort((a, b) => { - if (!isUndefined(a) && !isUndefined(b)) { - return path - .basename(a.name) - .toLowerCase() - .localeCompare(path.basename(b.name).toLowerCase()); - } - }); - // Push the first file (cover) to our extraction target - extractionTargets.push(files[0].name); - filesToWriteToDisk.coverFile = files[0].name; - if (!isEmpty(comicInfoXMLFileObject)) { - filesToWriteToDisk.comicInfoXML = comicInfoXMLFileObject[0].name; - extractionTargets.push(filesToWriteToDisk.comicInfoXML); - } - // Extract the files. - await p7zip.extract( - filePath, - targetDirectory, - extractionTargets, - "", - false - ); - - // ComicInfoXML detection, parsing and conversion to JSON - // Write ComicInfo.xml to disk - let comicinfostring = ""; - const comicInfoXMLPromise = new Promise((resolve, reject) => { - if ( - !isNil(filesToWriteToDisk.comicInfoXML) && - existsSync( - `${targetDirectory}/${path.basename( - filesToWriteToDisk.comicInfoXML - )}` - ) - ) { - let comicinfoString = ""; - const comicInfoXMLStream = createReadStream( - `${targetDirectory}/${path.basename( - filesToWriteToDisk.comicInfoXML - )}` - ); - comicInfoXMLStream.on("data", (data) => (comicinfoString += data)); - comicInfoXMLStream.on("end", async () => { - const comicInfoJSON = await convertXMLToJSON( - comicinfoString.toString() - ); - resolve({ - comicInfoJSON: comicInfoJSON.comicinfo, - }); - }); - } else { - resolve({ - comicInfoJSON: null, - }); - } - }); - // Write the cover to disk - const coverFilePromise = new Promise((resolve, reject) => { - const sharpStream = sharp().resize(275).toFormat("png"); - const coverStream = createReadStream( - `${targetDirectory}/${path.basename(filesToWriteToDisk.coverFile)}` + // only allow allowed image formats + remove( + filesFromArchive.files, + ({ name }) => + !IMPORT_IMAGE_FILE_FORMATS.includes(path.extname(name).toLowerCase()) ); - coverStream - .pipe(sharpStream) - .toFile( + + // detect ComicInfo.xml + const comicInfoXMLFileObject = remove( + filesFromArchive.files, + (file) => path.basename(file.name.toLowerCase()) === "comicinfo.xml" + ); + + // Natural sort + const files = filesFromArchive.files.sort((a, b) => { + if (!isUndefined(a) && !isUndefined(b)) { + return path + .basename(a.name) + .toLowerCase() + .localeCompare(path.basename(b.name).toLowerCase()); + } + }); + // Push the first file (cover) to our extraction target + extractionTargets.push(files[0].name); + filesToWriteToDisk.coverFile = files[0].name; + if (!isEmpty(comicInfoXMLFileObject)) { + filesToWriteToDisk.comicInfoXML = comicInfoXMLFileObject[0].name; + extractionTargets.push(filesToWriteToDisk.comicInfoXML); + } + // Extract the files. + await p7zip.extract( + filePath, + targetDirectory, + extractionTargets, + "", + false + ); + + // ComicInfoXML detection, parsing and conversion to JSON + // Write ComicInfo.xml to disk + let comicinfostring = ""; + const comicInfoXMLPromise = new Promise((resolve, reject) => { + if ( + !isNil(filesToWriteToDisk.comicInfoXML) && + existsSync( + `${targetDirectory}/${path.basename( + filesToWriteToDisk.comicInfoXML + )}` + ) + ) { + let comicinfoString = ""; + const comicInfoXMLStream = createReadStream( + `${targetDirectory}/${path.basename( + filesToWriteToDisk.comicInfoXML + )}` + ); + comicInfoXMLStream.on( + "data", + (data) => (comicinfoString += data) + ); + comicInfoXMLStream.on("end", async () => { + const comicInfoJSON = await convertXMLToJSON( + comicinfoString.toString() + ); + resolve({ + comicInfoJSON: comicInfoJSON.comicinfo, + }); + }); + } else { + resolve({ + comicInfoJSON: null, + }); + } + }); + // Write the cover to disk + const coverFilePromise = new Promise((resolve, reject) => { + const sharpStream = sharp().resize(275).toFormat("png"); + const coverStream = createReadStream( `${targetDirectory}/${path.basename( filesToWriteToDisk.coverFile - )}`, - (err, info) => { - if (err) { - reject(err); - } - // Update metadata - resolve({ - filePath, - name: fileNameWithoutExtension, - extension, - containedIn: targetDirectory, - fileSize: fse.statSync(filePath).size, - cover: { - filePath: path.relative( - process.cwd(), - `${targetDirectory}/${path.basename( - filesToWriteToDisk.coverFile - )}` - ), - }, - }); - } + )}` ); - }); + coverStream + .pipe(sharpStream) + .toFile( + `${targetDirectory}/${path.basename( + filesToWriteToDisk.coverFile + )}`, + (err, info) => { + if (err) { + reject(err); + } + // Update metadata + resolve({ + filePath, + name: fileNameWithoutExtension, + extension, + containedIn: targetDirectory, + fileSize: fse.statSync(filePath).size, + cover: { + filePath: path.relative( + process.cwd(), + `${targetDirectory}/${path.basename( + filesToWriteToDisk.coverFile + )}` + ), + }, + }); + } + ); + }); - return Promise.all([comicInfoXMLPromise, coverFilePromise]); + return Promise.all([comicInfoXMLPromise, coverFilePromise]); + } catch (err) { + reject(err); + } }; export const extractFromArchive = async (filePath: string) => {