🔧 Fix for a gnarly extension check in both unrar and unzip methods
This commit is contained in:
@@ -32,7 +32,7 @@ SOFTWARE.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createReadStream, createWriteStream, existsSync, statSync } from "fs";
|
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 * as p7zip from "p7zip-threetwo";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import sharp from "sharp";
|
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 (
|
export const extractComicInfoXMLFromRar = async (
|
||||||
filePath: string
|
filePath: string
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
const result = {
|
try {
|
||||||
filePath,
|
const result = {
|
||||||
};
|
filePath,
|
||||||
|
};
|
||||||
|
|
||||||
// Create the target directory
|
// Create the target directory
|
||||||
const directoryOptions = {
|
const directoryOptions = {
|
||||||
mode: 0o2775,
|
mode: 0o2775,
|
||||||
};
|
};
|
||||||
const { fileNameWithoutExtension, extension } =
|
const { fileNameWithoutExtension, extension } =
|
||||||
getFileConstituents(filePath);
|
getFileConstituents(filePath);
|
||||||
const targetDirectory = `${USERDATA_DIRECTORY}/covers/${fileNameWithoutExtension}`;
|
const targetDirectory = `${USERDATA_DIRECTORY}/covers/${fileNameWithoutExtension}`;
|
||||||
await createDirectory(directoryOptions, targetDirectory);
|
await createDirectory(directoryOptions, targetDirectory);
|
||||||
|
|
||||||
const archive = new Unrar({
|
const archive = new Unrar({
|
||||||
path: path.resolve(filePath),
|
path: path.resolve(filePath),
|
||||||
bin: `${UNRAR_BIN_PATH}`, // this will change depending on Docker base OS
|
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);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
remove(filesInArchive, ({ type }) => type === "Directory");
|
const filesInArchive: [RarFile] = await new Promise(
|
||||||
const comicInfoXML = remove(
|
(resolve, reject) => {
|
||||||
filesInArchive,
|
return archive.list((err, entries) => {
|
||||||
({ name }) => path.basename(name).toLowerCase() === "comicinfo.xml"
|
if(err) {
|
||||||
);
|
reject(err);
|
||||||
remove(
|
}
|
||||||
filesInArchive,
|
resolve(entries);
|
||||||
({ 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}`
|
|
||||||
);
|
|
||||||
|
|
||||||
archive.stream(comicInfoXML[0]["name"]).pipe(writeStream);
|
remove(filesInArchive, ({ type }) => type === "Directory");
|
||||||
writeStream.on("finish", async () => {
|
const comicInfoXML = remove(
|
||||||
const readStream = createReadStream(
|
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}`
|
`${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));
|
} else {
|
||||||
readStream.on("end", async () => {
|
resolve({ comicInfoJSON: null });
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
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 (
|
export const extractComicInfoXMLFromZip = async (
|
||||||
filePath: string
|
filePath: string
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
// Create the target directory
|
try {
|
||||||
const directoryOptions = {
|
// Create the target directory
|
||||||
mode: 0o2775,
|
const directoryOptions = {
|
||||||
};
|
mode: 0o2775,
|
||||||
const { fileNameWithoutExtension, extension } =
|
};
|
||||||
getFileConstituents(filePath);
|
const { fileNameWithoutExtension, extension } =
|
||||||
const targetDirectory = `${USERDATA_DIRECTORY}/covers/${fileNameWithoutExtension}`;
|
getFileConstituents(filePath);
|
||||||
await createDirectory(directoryOptions, targetDirectory);
|
const targetDirectory = `${USERDATA_DIRECTORY}/covers/${fileNameWithoutExtension}`;
|
||||||
|
await createDirectory(directoryOptions, targetDirectory);
|
||||||
|
|
||||||
let filesToWriteToDisk = { coverFile: null, comicInfoXML: null };
|
let filesToWriteToDisk = { coverFile: null, comicInfoXML: null };
|
||||||
const extractionTargets = [];
|
const extractionTargets = [];
|
||||||
|
|
||||||
// read the archive
|
// read the archive
|
||||||
let filesFromArchive = await p7zip.read(path.resolve(filePath));
|
let filesFromArchive = await p7zip.read(path.resolve(filePath));
|
||||||
|
|
||||||
// only allow allowed image formats
|
// only allow allowed image formats
|
||||||
remove(
|
remove(
|
||||||
filesFromArchive.files,
|
filesFromArchive.files,
|
||||||
({ name }) => !IMPORT_IMAGE_FILE_FORMATS.includes(path.extname(name))
|
({ name }) =>
|
||||||
);
|
!IMPORT_IMAGE_FILE_FORMATS.includes(path.extname(name).toLowerCase())
|
||||||
|
|
||||||
// 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)}`
|
|
||||||
);
|
);
|
||||||
coverStream
|
|
||||||
.pipe(sharpStream)
|
// detect ComicInfo.xml
|
||||||
.toFile(
|
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(
|
`${targetDirectory}/${path.basename(
|
||||||
filesToWriteToDisk.coverFile
|
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) => {
|
export const extractFromArchive = async (filePath: string) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user