153 lines
4.3 KiB
JavaScript
153 lines
4.3 KiB
JavaScript
|
/* global window */
|
||
|
const spawn = require('child_process').spawn;
|
||
|
const fs = require('fs');
|
||
|
const tmp = require('tmp');
|
||
|
const user = require('./user');
|
||
|
|
||
|
const outputFormats = {
|
||
|
asciidoc: 'text/plain',
|
||
|
context: 'application/x-latex',
|
||
|
epub: 'application/epub+zip',
|
||
|
epub3: 'application/epub+zip',
|
||
|
latex: 'application/x-latex',
|
||
|
odt: 'application/vnd.oasis.opendocument.text',
|
||
|
pdf: 'application/pdf',
|
||
|
rst: 'text/plain',
|
||
|
rtf: 'application/rtf',
|
||
|
textile: 'text/plain',
|
||
|
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
|
};
|
||
|
|
||
|
const highlightStyles = [
|
||
|
'pygments',
|
||
|
'kate',
|
||
|
'monochrome',
|
||
|
'espresso',
|
||
|
'zenburn',
|
||
|
'haddock',
|
||
|
'tango',
|
||
|
];
|
||
|
|
||
|
const readJson = (str) => {
|
||
|
try {
|
||
|
return JSON.parse(str);
|
||
|
} catch (e) {
|
||
|
return {};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
exports.generate = (req, res) => {
|
||
|
let pandocError = '';
|
||
|
const outputFormat = Object.prototype.hasOwnProperty.call(outputFormats, req.query.format)
|
||
|
? req.query.format
|
||
|
: 'pdf';
|
||
|
Promise.all([
|
||
|
user.checkSponsor(req.query.idToken),
|
||
|
user.checkMonetize(req.query.token),
|
||
|
])
|
||
|
.then(([isSponsor, isMonetize]) => {
|
||
|
if (!isSponsor && !isMonetize) {
|
||
|
throw new Error('unauthorized');
|
||
|
}
|
||
|
|
||
|
return new Promise((resolve, reject) => {
|
||
|
tmp.file({
|
||
|
postfix: `.${outputFormat}`,
|
||
|
}, (err, filePath, fd, cleanupCallback) => {
|
||
|
if (err) {
|
||
|
reject(err);
|
||
|
} else {
|
||
|
resolve({
|
||
|
filePath,
|
||
|
cleanupCallback,
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
})
|
||
|
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
|
||
|
const options = readJson(req.query.options);
|
||
|
const metadata = readJson(req.query.metadata);
|
||
|
const params = [];
|
||
|
|
||
|
params.push('--latex-engine=xelatex');
|
||
|
params.push('--webtex=http://chart.apis.google.com/chart?cht=tx&chf=bg,s,FFFFFF00&chco=000000&chl=');
|
||
|
if (options.toc) {
|
||
|
params.push('--toc');
|
||
|
}
|
||
|
options.tocDepth = parseInt(options.tocDepth, 10);
|
||
|
if (!isNaN(options.tocDepth)) {
|
||
|
params.push('--toc-depth', options.tocDepth);
|
||
|
}
|
||
|
options.highlightStyle = highlightStyles.indexOf(options.highlightStyle) !== -1 ? options.highlightStyle : 'kate';
|
||
|
params.push('--highlight-style', options.highlightStyle);
|
||
|
Object.keys(metadata).forEach((key) => {
|
||
|
params.push('-M', `${key}=${metadata[key]}`);
|
||
|
});
|
||
|
|
||
|
let finished = false;
|
||
|
|
||
|
function onError(error) {
|
||
|
finished = true;
|
||
|
cleanupCallback();
|
||
|
reject(error);
|
||
|
}
|
||
|
|
||
|
const binPath = process.env.PANDOC_PATH || 'pandoc';
|
||
|
const format = outputFormat === 'pdf' ? 'latex' : outputFormat;
|
||
|
params.push('-f', 'json', '-t', format, '-o', filePath);
|
||
|
const pandoc = spawn(binPath, params, {
|
||
|
stdio: [
|
||
|
'pipe',
|
||
|
'ignore',
|
||
|
'pipe',
|
||
|
],
|
||
|
});
|
||
|
let timeoutId = setTimeout(() => {
|
||
|
timeoutId = null;
|
||
|
pandoc.kill();
|
||
|
}, 50000);
|
||
|
pandoc.on('error', onError);
|
||
|
pandoc.stdin.on('error', onError);
|
||
|
pandoc.stderr.on('data', (data) => {
|
||
|
pandocError += `${data}`;
|
||
|
});
|
||
|
pandoc.on('close', (code) => {
|
||
|
if (!finished) {
|
||
|
clearTimeout(timeoutId);
|
||
|
if (!timeoutId) {
|
||
|
res.statusCode = 408;
|
||
|
cleanupCallback();
|
||
|
reject(new Error('timeout'));
|
||
|
} else if (code) {
|
||
|
cleanupCallback();
|
||
|
reject();
|
||
|
} else {
|
||
|
res.set('Content-Type', outputFormats[outputFormat]);
|
||
|
const readStream = fs.createReadStream(filePath);
|
||
|
readStream.on('open', () => readStream.pipe(res));
|
||
|
readStream.on('close', () => cleanupCallback());
|
||
|
readStream.on('error', () => {
|
||
|
cleanupCallback();
|
||
|
reject();
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
req.pipe(pandoc.stdin);
|
||
|
}))
|
||
|
.catch((err) => {
|
||
|
const message = err && err.message;
|
||
|
if (message === 'unauthorized') {
|
||
|
res.statusCode = 401;
|
||
|
res.end('Unauthorized.');
|
||
|
} else if (message === 'timeout') {
|
||
|
res.statusCode = 408;
|
||
|
res.end('Request timeout.');
|
||
|
} else {
|
||
|
res.statusCode = 400;
|
||
|
res.end(pandocError || 'Unknown error.');
|
||
|
}
|
||
|
});
|
||
|
};
|