/**
|
|
* 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.
|
|
*/
|
|
|
|
const async = require('async');
|
|
const fs = require('fs');
|
|
const log4js = require('log4js');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const settings = require('./Settings');
|
|
const spawn = require('child_process').spawn;
|
|
|
|
// Conversion tasks will be queued up, so we don't overload the system
|
|
const queue = async.queue(doConvertTask, 1);
|
|
|
|
const 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
|
|
const 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,
|
|
destFile: destFile.replace(/\.doc$/, '.odt'),
|
|
type: 'odt',
|
|
callback() {
|
|
queue.push({srcFile: srcFile.replace(/\.html$/, '.odt'), destFile, type, callback, fileExtension});
|
|
},
|
|
});
|
|
} else {
|
|
queue.push({srcFile, destFile, type, callback, fileExtension});
|
|
}
|
|
};
|
|
|
|
function doConvertTask(task, callback) {
|
|
const 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}`);
|
|
const 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);
|
|
|
|
let stdoutBuffer = '';
|
|
|
|
// Delegate the processing of stdout to another function
|
|
soffice.stdout.on('data', (data) => {
|
|
stdoutBuffer += data.toString();
|
|
});
|
|
|
|
// Append error messages to the buffer
|
|
soffice.stderr.on('data', (data) => {
|
|
stdoutBuffer += data.toString();
|
|
});
|
|
|
|
soffice.on('exit', (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) {
|
|
const filename = path.basename(task.srcFile);
|
|
const sourceFilename = `${filename.substr(0, filename.lastIndexOf('.'))}.${task.fileExtension}`;
|
|
const sourcePath = path.join(tmpDir, sourceFilename);
|
|
libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`);
|
|
fs.rename(sourcePath, task.destFile, callback);
|
|
},
|
|
], (err) => {
|
|
// Invoke the callback for the local queue
|
|
callback();
|
|
|
|
// Invoke the callback for the task
|
|
task.callback(err);
|
|
});
|
|
}
|