const fs = require('fs-extra'); const iconv = require('iconv-lite'); const he = require('he'); const LimitedQueue = require('../../LimitedQueue'); const textUtils = require('./textUtils'); const utils = require('../../utils'); const queue = new LimitedQueue(3, 20, 3*60*1000);//3 минуты ожидание подвижек class ConvertBase { constructor(config) { this.config = config; this.calibrePath = `${config.dataDir}/calibre/ebook-convert`; this.sofficePath = '/usr/bin/soffice'; this.pdfToHtmlPath = '/usr/bin/pdftohtml'; } async run(data, opts) {// eslint-disable-line no-unused-vars //override } async checkExternalConverterPresent() { if (!await fs.pathExists(this.calibrePath)) throw new Error('Внешний конвертер calibre не найден'); if (!await fs.pathExists(this.sofficePath)) throw new Error('Внешний конвертер LibreOffice не найден'); if (!await fs.pathExists(this.pdfToHtmlPath)) throw new Error('Внешний конвертер pdftohtml не найден'); } async execConverter(path, args, onData, abort) { onData = (onData ? onData : () => {}); let q = null; try { q = await queue.get(() => {onData();}); } catch (e) { throw new Error('Слишком большая очередь конвертирования. Пожалуйста, попробуйте позже.'); } abort = (abort ? abort : () => false); const myAbort = () => { return q.abort() || abort(); } try { if (myAbort()) throw new Error('abort'); const result = await utils.spawnProcess(path, { killAfter: 3600,//1 час args, onData: (data) => { if (queue.freed > 0) q.resetTimeout(); onData(data); }, //будем периодически проверять работу конвертера и если очереди нет, то разрешаем работу пинком onData onUsage: (stats) => { if (queue.freed > 0 && stats.cpu >= 10) { q.resetTimeout(); onData('.'); } }, onUsageInterval: 10, abort: myAbort }); if (result.code != 0) { const error = `${result.code}|FORLOG|, exec: ${path}, args: ${args.join(' ')}, stdout: ${result.stdout}, stderr: ${result.stderr}`; throw new Error(`Внешний конвертер завершился с ошибкой: ${error}`); } } catch(e) { if (e.status == 'killed') { throw new Error('Слишком долгое ожидание конвертера'); } else if (e.status == 'abort') { throw new Error('abort'); } else if (e.status == 'error') { throw new Error(e.error); } else { throw new Error(e); } } finally { q.ret(); } } decode(data) { let selected = textUtils.getEncoding(data); if (selected.toLowerCase() != 'utf-8') return iconv.decode(data, selected); else return data; } repSpaces(text) { return text.replace(/ |[\t\n\r]/g, ' '); } escapeEntities(text) { return he.escape(he.decode(text.replace(/ /g, ' '))); } formatFb2(fb2) { let out = ''; out += ''; out += this.formatFb2Node(fb2); out += ''; return out; } formatFb2Node(node, name) { let out = ''; if (Array.isArray(node)) { for (const n of node) { out += this.formatFb2Node(n); } } else if (typeof node == 'string') { if (name) out += `<${name}>${this.repSpaces(node)}`; else out += this.repSpaces(node); } else { if (node._n) name = node._n; let attrs = ''; if (node._attrs) { for (let attrName in node._attrs) { attrs += ` ${attrName}="${node._attrs[attrName]}"`; } } let tOpen = ''; let tBody = ''; let tClose = ''; if (name) tOpen += `<${name}${attrs}>`; if (node.hasOwnProperty('_t')) tBody += this.repSpaces(node._t); for (let nodeName in node) { if (nodeName && nodeName[0] == '_' && nodeName != '_a') continue; const n = node[nodeName]; tBody += this.formatFb2Node(n, nodeName); } if (name) tClose += ``; if (attrs == '' && name == 'p' && tBody.trim() == '') out += '' else out += `${tOpen}${tBody}${tClose}`; } return out; } } module.exports = ConvertBase;