pad.libre-service.eu-etherpad/src/node/utils/LibreOffice.js
Richard Hansen 6665c4693f Clear hang timeout timer when LibreOffice exits
This prevents `npm test` from freezing for two minutes after the tests
complete.

Also switch to an arrow function for the `setTimeout` callback.
2020-11-24 10:04:14 +00:00

138 lines
4.7 KiB
JavaScript

/**
* Controls the communication with LibreOffice
*/
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var async = require("async");
var fs = require("fs");
var log4js = require('log4js');
var os = require("os");
var path = require("path");
var settings = require("./Settings");
var spawn = require("child_process").spawn;
// Conversion tasks will be queued up, so we don't overload the system
var queue = async.queue(doConvertTask, 1);
var libreOfficeLogger = log4js.getLogger('LibreOffice');
/**
* Convert a file from one type to another
*
* @param {String} srcFile The path on disk to convert
* @param {String} destFile The path on disk where the converted file should be stored
* @param {String} type The type to convert into
* @param {Function} callback Standard callback function
*/
exports.convertFile = function(srcFile, destFile, type, callback) {
// Used for the moving of the file, not the conversion
var fileExtension = type;
if (type === "html") {
// "html:XHTML Writer File:UTF8" does a better job than normal html exports
if (path.extname(srcFile).toLowerCase() === ".doc") {
type = "html";
}
// PDF files need to be converted with LO Draw ref https://github.com/ether/etherpad-lite/issues/4151
if (path.extname(srcFile).toLowerCase() === ".pdf") {
type = "html:XHTML Draw File"
}
}
// soffice can't convert from html to doc directly (verified with LO 5 and 6)
// we need to convert to odt first, then to doc
// to avoid `Error: no export filter for /tmp/xxxx.doc` error
if (type === 'doc') {
queue.push({
"srcFile": srcFile,
"destFile": destFile.replace(/\.doc$/, '.odt'),
"type": 'odt',
"callback": function () {
queue.push({"srcFile": srcFile.replace(/\.html$/, '.odt'), "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension });
}
});
} else {
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension});
}
};
function doConvertTask(task, callback) {
var tmpDir = os.tmpdir();
async.series([
/*
* use LibreOffice to convert task.srcFile to another format, given in
* task.type
*/
function(callback) {
libreOfficeLogger.debug(`Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}`);
var soffice = spawn(settings.soffice, [
'--headless',
'--invisible',
'--nologo',
'--nolockcheck',
'--writer',
'--convert-to', task.type,
task.srcFile,
'--outdir', tmpDir
]);
// Soffice/libreoffice is buggy and often hangs.
// To remedy this we kill the spawned process after a while.
const hangTimeout = setTimeout(() => {
soffice.stdin.pause(); // required to kill hanging threads
soffice.kill();
}, 120000);
var stdoutBuffer = '';
// Delegate the processing of stdout to another function
soffice.stdout.on('data', function(data) {
stdoutBuffer += data.toString();
});
// Append error messages to the buffer
soffice.stderr.on('data', function(data) {
stdoutBuffer += data.toString();
});
soffice.on('exit', function(code) {
clearTimeout(hangTimeout);
if (code != 0) {
// Throw an exception if libreoffice failed
return callback(`LibreOffice died with exit code ${code} and message: ${stdoutBuffer}`);
}
// if LibreOffice exited succesfully, go on with processing
callback();
})
},
// Move the converted file to the correct place
function(callback) {
var filename = path.basename(task.srcFile);
var sourceFilename = filename.substr(0, filename.lastIndexOf('.')) + '.' + task.fileExtension;
var sourcePath = path.join(tmpDir, sourceFilename);
libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`);
fs.rename(sourcePath, task.destFile, callback);
}
], function(err) {
// Invoke the callback for the local queue
callback();
// Invoke the callback for the task
task.callback(err);
});
}