import/export: Promisify Abiword and LibreOffice conversion

This commit is contained in:
Richard Hansen 2021-03-18 01:01:47 -04:00 committed by John McLear
parent b321267e66
commit b2c0837cf5
4 changed files with 67 additions and 91 deletions

View file

@ -98,8 +98,7 @@ exports.doExport = async (req, res, padId, readOnlyId, type) => {
settings.soffice != null ? require('../utils/LibreOffice') settings.soffice != null ? require('../utils/LibreOffice')
: settings.abiword != null ? require('../utils/Abiword') : settings.abiword != null ? require('../utils/Abiword')
: null; : null;
// @TODO no Promise interface for converters (yet) await converter.convertFile(srcFile, destFile, type);
await util.promisify(converter.convertFile)(srcFile, destFile, type);
} }
// send the file // send the file

View file

@ -179,17 +179,12 @@ const doImport = async (req, res, padId) => {
// if no converter only rename // if no converter only rename
await fs.rename(srcFile, destFile); await fs.rename(srcFile, destFile);
} else { } else {
// @TODO - no Promise interface for converters (yet) try {
await new Promise((resolve, reject) => { await converter.convertFile(srcFile, destFile, exportExtension);
converter.convertFile(srcFile, destFile, exportExtension, (err) => { } catch (err) {
// catch convert errors logger.warn(`Converting Error: ${err.stack || err}`);
if (err) { throw new ImportError('convertFailed');
logger.warn(`Converting Error: ${err.stack || err}`); }
return reject(new ImportError('convertFailed'));
}
resolve();
});
});
} }
} }

View file

@ -24,28 +24,24 @@ const async = require('async');
const settings = require('./Settings'); const settings = require('./Settings');
const os = require('os'); const os = require('os');
let doConvertTask;
// on windows we have to spawn a process for each convertion, // on windows we have to spawn a process for each convertion,
// cause the plugin abicommand doesn't exist on this platform // cause the plugin abicommand doesn't exist on this platform
if (os.type().indexOf('Windows') > -1) { if (os.type().indexOf('Windows') > -1) {
doConvertTask = (task, callback) => { exports.convertFile = async (srcFile, destFile, type) => {
const abiword = spawn(settings.abiword, [`--to=${task.destFile}`, task.srcFile]); const abiword = spawn(settings.abiword, [`--to=${destFile}`, srcFile]);
let stdoutBuffer = ''; let stdoutBuffer = '';
abiword.stdout.on('data', (data) => { stdoutBuffer += data.toString(); }); abiword.stdout.on('data', (data) => { stdoutBuffer += data.toString(); });
abiword.stderr.on('data', (data) => { stdoutBuffer += data.toString(); }); abiword.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
abiword.on('exit', (code) => { await new Promise((resolve, reject) => {
if (code !== 0) return callback(new Error(`Abiword died with exit code ${code}`)); abiword.on('exit', (code) => {
if (stdoutBuffer !== '') { if (code !== 0) return reject(new Error(`Abiword died with exit code ${code}`));
console.log(stdoutBuffer); if (stdoutBuffer !== '') {
} console.log(stdoutBuffer);
callback(); }
resolve();
});
}); });
}; };
exports.convertFile = (srcFile, destFile, type, callback) => {
doConvertTask({srcFile, destFile, type}, callback);
};
// on unix operating systems, we can start abiword with abicommand and // on unix operating systems, we can start abiword with abicommand and
// communicate with it via stdin/stdout // communicate with it via stdin/stdout
// thats much faster, about factor 10 // thats much faster, about factor 10
@ -85,7 +81,7 @@ if (os.type().indexOf('Windows') > -1) {
}; };
}, 1); }, 1);
exports.convertFile = (srcFile, destFile, type, callback) => { exports.convertFile = async (srcFile, destFile, type) => {
queue.push({srcFile, destFile, type}, callback); await queue.pushAsync({srcFile, destFile, type});
}; };
} }

View file

@ -18,7 +18,7 @@
*/ */
const async = require('async'); const async = require('async');
const fs = require('fs'); const fs = require('fs').promises;
const log4js = require('log4js'); const log4js = require('log4js');
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
@ -27,57 +27,55 @@ const spawn = require('child_process').spawn;
const libreOfficeLogger = log4js.getLogger('LibreOffice'); const libreOfficeLogger = log4js.getLogger('LibreOffice');
const doConvertTask = (task, callback) => { const doConvertTask = async (task) => {
const tmpDir = os.tmpdir(); const tmpDir = os.tmpdir();
async.series([ libreOfficeLogger.debug(
(callback) => { `Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}`);
libreOfficeLogger.debug( const soffice = spawn(settings.soffice, [
`Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}` '--headless',
); '--invisible',
const soffice = spawn(settings.soffice, [ '--nologo',
'--headless', '--nolockcheck',
'--invisible', '--writer',
'--nologo', '--convert-to',
'--nolockcheck', task.type,
'--writer', task.srcFile,
'--convert-to', '--outdir',
task.type, tmpDir,
task.srcFile, ]);
'--outdir', // Soffice/libreoffice is buggy and often hangs.
tmpDir, // To remedy this we kill the spawned process after a while.
]); const hangTimeout = setTimeout(() => {
// Soffice/libreoffice is buggy and often hangs. soffice.stdin.pause(); // required to kill hanging threads
// To remedy this we kill the spawned process after a while. soffice.kill();
const hangTimeout = setTimeout(() => { }, 120000);
soffice.stdin.pause(); // required to kill hanging threads let stdoutBuffer = '';
soffice.kill(); soffice.stdout.on('data', (data) => { stdoutBuffer += data.toString(); });
}, 120000); soffice.stderr.on('data', (data) => { stdoutBuffer += data.toString(); });
let stdoutBuffer = ''; await new Promise((resolve, reject) => {
soffice.stdout.on('data', (data) => { stdoutBuffer += data.toString(); }); soffice.on('exit', (code) => {
soffice.stderr.on('data', (data) => { stdoutBuffer += data.toString(); }); clearTimeout(hangTimeout);
soffice.on('exit', (code) => { if (code !== 0) {
clearTimeout(hangTimeout); return reject(
if (code !== 0) { new Error(`LibreOffice died with exit code ${code} and message: ${stdoutBuffer}`));
return callback( }
new Error(`LibreOffice died with exit code ${code} and message: ${stdoutBuffer}`)); resolve();
} });
callback(); });
});
},
(callback) => { const filename = path.basename(task.srcFile);
const filename = path.basename(task.srcFile); const sourceFile = `${filename.substr(0, filename.lastIndexOf('.'))}.${task.fileExtension}`;
const sourceFile = `${filename.substr(0, filename.lastIndexOf('.'))}.${task.fileExtension}`; const sourcePath = path.join(tmpDir, sourceFile);
const sourcePath = path.join(tmpDir, sourceFile); libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`);
libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`); await fs.rename(sourcePath, task.destFile);
fs.rename(sourcePath, task.destFile, callback);
},
], callback);
}; };
// Conversion tasks will be queued up, so we don't overload the system // Conversion tasks will be queued up, so we don't overload the system
const queue = async.queue(doConvertTask, 1); const queue = async.queue(
// For some reason util.callbackify() throws "TypeError [ERR_INVALID_ARG_TYPE]: The last
// argument must be of type Function. Received type object" on Node.js 10.x.
(task, cb) => doConvertTask(task).then(() => cb(), (err) => cb(err || new Error(err))), 1);
/** /**
* Convert a file from one type to another * Convert a file from one type to another
@ -87,7 +85,7 @@ const queue = async.queue(doConvertTask, 1);
* @param {String} type The type to convert into * @param {String} type The type to convert into
* @param {Function} callback Standard callback function * @param {Function} callback Standard callback function
*/ */
exports.convertFile = (srcFile, destFile, type, callback) => { exports.convertFile = async (srcFile, destFile, type) => {
// Used for the moving of the file, not the conversion // Used for the moving of the file, not the conversion
const fileExtension = type; const fileExtension = type;
@ -107,21 +105,9 @@ exports.convertFile = (srcFile, destFile, type, callback) => {
// to avoid `Error: no export filter for /tmp/xxxx.doc` error // to avoid `Error: no export filter for /tmp/xxxx.doc` error
if (type === 'doc') { if (type === 'doc') {
const intermediateFile = destFile.replace(/\.doc$/, '.odt'); const intermediateFile = destFile.replace(/\.doc$/, '.odt');
async.series([ await queue.pushAsync({srcFile, destFile: intermediateFile, type: 'odt', fileExtension: 'odt'});
(callback) => queue.push({ await queue.pushAsync({srcFile: intermediateFile, destFile, type, fileExtension});
srcFile,
destFile: intermediateFile,
type: 'odt',
fileExtension: 'odt',
}, callback),
(callback) => queue.push({
srcFile: intermediateFile,
destFile,
type,
fileExtension,
}, callback),
], callback);
} else { } else {
queue.push({srcFile, destFile, type, fileExtension}, callback); await queue.pushAsync({srcFile, destFile, type, fileExtension});
} }
}; };