Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
25 changes: 20 additions & 5 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}%',
Expand All @@ -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());
Expand All @@ -44,6 +46,8 @@ async function main({ inputFile, outputDir, override }) {
onStart,
onUpdate,
onFinish,
override,
ignoreWriteErrors,
});
} catch (error) {
progressBar.stop();
Expand All @@ -54,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`
);
}
}
}
}
Expand All @@ -74,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'
Expand All @@ -93,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;
63 changes: 49 additions & 14 deletions lib/wpress-extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -39,10 +49,22 @@ async function readHeader(fd) {
};
}

async function readBlockToFile(fd, header, outputPath) {
const outputFilePath = path.join(outputPath, header.prefix, header.name);
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) {
Expand All @@ -62,7 +84,13 @@ 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({
Expand All @@ -72,6 +100,7 @@ module.exports = async function wpExtract({
onUpdate,
onFinish,
override,
ignoreWriteErrors,
}) {
if (!fs.existsSync(_inputFile)) {
throw new Error(
Expand All @@ -82,12 +111,10 @@ module.exports = async function wpExtract({
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);
Expand All @@ -97,17 +124,25 @@ module.exports = async function wpExtract({
onStart(inputFileStat.size);

let offset = 0;
let countFiles = 0;
const counts = { success: 0, error: 0 };

while (true) {
const header = await readHeader(inputFile);
if (!header) {
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);
Expand All @@ -116,5 +151,5 @@ module.exports = async function wpExtract({
await inputFile.close();

// Trigger onFinish callback
onFinish(countFiles);
onFinish(counts);
};