From 942cd27ac0faba76c31806ca8b67c47324e38553 Mon Sep 17 00:00:00 2001 From: Alex Diller Date: Wed, 13 Sep 2023 09:35:27 +0200 Subject: [PATCH 1/4] fix issues with invalid windows file name --- lib/wpress-extract.js | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/wpress-extract.js b/lib/wpress-extract.js index 1a33666..9992ed1 100644 --- a/lib/wpress-extract.js +++ b/lib/wpress-extract.js @@ -5,6 +5,16 @@ const path = require('path'); const HEADER_SIZE = 4377; // length of the header const HEADER_CHUNK_EOF = Buffer.alloc(HEADER_SIZE); // Empty header used for check if we reached the end +const IS_WIN = process.platform === 'win32'; + +function convertToValidWindowsFilename(input) { + return IS_WIN ? input.replace(/[|\\:*?"<>]/g, '') : input; +} + +function convertToValidWindowsFilepath(input) { + return IS_WIN ? input.replace(/[|\\:*?"<>]/g, '') : input; +} + function isDirEmpty(dirname) { return fs.promises.readdir(dirname).then((files) => { return files.length === 0; @@ -40,7 +50,11 @@ async function readHeader(fd) { } async function readBlockToFile(fd, header, outputPath) { - const outputFilePath = path.join(outputPath, header.prefix, header.name); + const outputFilePath = path.join( + outputPath, + convertToValidWindowsFilepath(header.prefix), + convertToValidWindowsFilename(header.name) + ); fse.ensureDirSync(path.dirname(outputFilePath)); const outputStream = fs.createWriteStream(outputFilePath); @@ -65,18 +79,9 @@ async function readBlockToFile(fd, header, outputPath) { outputStream.close(); } -module.exports = async function wpExtract({ - inputFile: _inputFile, - outputDir, - onStart, - onUpdate, - onFinish, - override, -}) { +module.exports = async function wpExtract({ inputFile: _inputFile, outputDir, onStart, onUpdate, onFinish, override }) { if (!fs.existsSync(_inputFile)) { - throw new Error( - `Input file at location "${_inputFile}" could not be found.` - ); + throw new Error(`Input file at location "${_inputFile}" could not be found.`); } if (override) { @@ -84,14 +89,12 @@ module.exports = async function wpExtract({ fse.emptyDirSync(outputDir); } else { if (fs.existsSync(outputDir) && !(await isDirEmpty(outputDir))) { - throw new Error( - `Output dir is not empty. Clear it first or use the --force option to override it.` - ); + throw new Error(`Output dir is not empty. Clear it first or use the --force option to override it.`); } } const inputFileStat = fs.statSync(_inputFile); - const inputFile = await fs.promises.open(_inputFile, 'r'); + const inputFile = await fs.promises.open(_inputFile, "r"); // Trigger onStart callback onStart(inputFileStat.size); From df9205dbc0962200092ffddcef60e87e70da2136 Mon Sep 17 00:00:00 2001 From: Alex Diller Date: Wed, 13 Sep 2023 10:13:44 +0200 Subject: [PATCH 2/4] fix --force (works now) --- cli.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cli.js b/cli.js index 8c604f6..bd76d7a 100755 --- a/cli.js +++ b/cli.js @@ -44,6 +44,7 @@ async function main({ inputFile, outputDir, override }) { onStart, onUpdate, onFinish, + override, }); } catch (error) { progressBar.stop(); From 7bb567398fafabba86f5dde0248e6a8af5dcad5c Mon Sep 17 00:00:00 2001 From: Alex Diller Date: Wed, 13 Sep 2023 10:15:18 +0200 Subject: [PATCH 3/4] add --ignore-write-errors --- cli.js | 24 +++++++++++++---- lib/wpress-extract.js | 60 +++++++++++++++++++++++++++++++++---------- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/cli.js b/cli.js index bd76d7a..986df7a 100755 --- a/cli.js +++ b/cli.js @@ -7,7 +7,7 @@ const cliProgress = require('cli-progress'); const wpExtract = require('./lib/wpress-extract'); -async function main({ inputFile, outputDir, override }) { +async function main({ inputFile, outputDir, override, ignoreWriteErrors }) { const progressBar = new cliProgress.SingleBar( { format: 'Progress: {bar} | {percentage}%', @@ -29,9 +29,11 @@ async function main({ inputFile, outputDir, override }) { }; let totalFiles = 0; + let errorFiles = 0; let success = false; - const onFinish = (_totalFiles) => { - totalFiles = _totalFiles; + const onFinish = (counts) => { + totalFiles = counts.success; + errorFiles = counts.error; success = true; // Set the progress bar to 100% and stop progressBar.update(progressBar.getTotal()); @@ -45,6 +47,7 @@ async function main({ inputFile, outputDir, override }) { onUpdate, onFinish, override, + ignoreWriteErrors, }); } catch (error) { progressBar.stop(); @@ -55,6 +58,11 @@ async function main({ inputFile, outputDir, override }) { if (success) { console.log(); console.log(`Successfully extracted ${totalFiles} files.`); + if (errorFiles > 0) { + console.error( + `${errorFiles} files were not extracted because of write error` + ); + } } } } @@ -75,13 +83,19 @@ yargs(hideBin(process.argv)).command( }) .option('f', { alias: 'force', - describe: 'override existing directory', + describe: 'Override existing directory', + type: 'boolean', + }) + .option('iwe', { + alias: 'ignore-write-errors', + describe: 'Ignore file write errors and continue', type: 'boolean', }); }, (argv) => { const override = !!argv.force; const inputFile = path.resolve(process.cwd(), argv.input); + const ignoreWriteErrors = !!argv.ignoreWriteErrors; let outputDir = typeof argv.out === 'string' @@ -94,6 +108,6 @@ yargs(hideBin(process.argv)).command( outputDir = path.join(process.cwd(), dirName); } - return main({ inputFile, outputDir, override }); + return main({ inputFile, outputDir, override, ignoreWriteErrors }); } ).argv; diff --git a/lib/wpress-extract.js b/lib/wpress-extract.js index 9992ed1..4a5b8f4 100644 --- a/lib/wpress-extract.js +++ b/lib/wpress-extract.js @@ -49,14 +49,22 @@ async function readHeader(fd) { }; } -async function readBlockToFile(fd, header, outputPath) { +async function readBlockToFile(fd, header, outputPath, ignoreWriteErrors) { const outputFilePath = path.join( outputPath, convertToValidWindowsFilepath(header.prefix), convertToValidWindowsFilename(header.name) ); fse.ensureDirSync(path.dirname(outputFilePath)); + const outputStream = fs.createWriteStream(outputFilePath); + let writeError = null; + if (ignoreWriteErrors) { + outputStream.on('error', (err) => { + console.error(outputFilePath, err); + writeError = err; + }); + } let totalBytesToRead = header.size; while (true) { @@ -76,31 +84,47 @@ async function readBlockToFile(fd, header, outputPath) { totalBytesToRead -= data.bytesRead; } - outputStream.close(); + return ignoreWriteErrors + ? new Promise((done, reject) => + outputStream.close((err) => + writeError ?? err ? reject(writeError ?? err) : done() + ) + ) + : outputStream.close(); } -module.exports = async function wpExtract({ inputFile: _inputFile, outputDir, onStart, onUpdate, onFinish, override }) { +module.exports = async function wpExtract({ + inputFile: _inputFile, + outputDir, + onStart, + onUpdate, + onFinish, + override, + ignoreWriteErrors, +}) { if (!fs.existsSync(_inputFile)) { - throw new Error(`Input file at location "${_inputFile}" could not be found.`); + throw new Error( + `Input file at location "${_inputFile}" could not be found.` + ); } if (override) { // Ensure the output dir exists and is empty fse.emptyDirSync(outputDir); - } else { - if (fs.existsSync(outputDir) && !(await isDirEmpty(outputDir))) { - throw new Error(`Output dir is not empty. Clear it first or use the --force option to override it.`); - } + } else if (fs.existsSync(outputDir) && !(await isDirEmpty(outputDir))) { + throw new Error( + `Output dir is not empty. Clear it first or use the --force option to override it.` + ); } const inputFileStat = fs.statSync(_inputFile); - const inputFile = await fs.promises.open(_inputFile, "r"); + const inputFile = await fs.promises.open(_inputFile, 'r'); // Trigger onStart callback onStart(inputFileStat.size); let offset = 0; - let countFiles = 0; + const counts = { success: 0, error: 0 }; while (true) { const header = await readHeader(inputFile); @@ -108,9 +132,17 @@ module.exports = async function wpExtract({ inputFile: _inputFile, outputDir, on break; } - await readBlockToFile(inputFile, header, outputDir); - offset = offset + HEADER_SIZE + header.size; - countFiles++; + try { + await readBlockToFile(inputFile, header, outputDir, ignoreWriteErrors); + counts.success++; + } catch (err) { + if (!ignoreWriteErrors) { + throw err; + } + console.error(err); + counts.error++; + } + offset += HEADER_SIZE + header.size; // Trigger onUpdate callback onUpdate(offset); @@ -119,5 +151,5 @@ module.exports = async function wpExtract({ inputFile: _inputFile, outputDir, on await inputFile.close(); // Trigger onFinish callback - onFinish(countFiles); + onFinish(counts); }; From 3f49e5fa5ce4f212c2b572cd8d8f65b78fa86ad7 Mon Sep 17 00:00:00 2001 From: Alex Diller Date: Wed, 13 Sep 2023 10:15:40 +0200 Subject: [PATCH 4/4] add .editorconfig --- .editorconfig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9611171 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = crlf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.js] +quote_type = single