diff --git a/client/api/reader.js b/client/api/reader.js index 2409aefa..c17cccef 100644 --- a/client/api/reader.js +++ b/client/api/reader.js @@ -27,6 +27,9 @@ class Reader { response = await wsc.message(requestId); callback(response); + if (!response.state) + throw new Error('Неверный ответ api'); + if (response.state == 'finish' || response.state == 'error') { break; } @@ -47,6 +50,9 @@ class Reader { response = response.data; callback(response); + if (!response.state) + throw new Error('Неверный ответ api'); + if (response.state == 'finish' || response.state == 'error') { break; } diff --git a/client/components/Reader/HelpPage/DonateHelpPage/DonateHelpPage.vue b/client/components/Reader/HelpPage/DonateHelpPage/DonateHelpPage.vue index 637ce6e7..4ae7dbfb 100644 --- a/client/components/Reader/HelpPage/DonateHelpPage/DonateHelpPage.vue +++ b/client/components/Reader/HelpPage/DonateHelpPage/DonateHelpPage.vue @@ -1,30 +1,54 @@ @@ -200,6 +247,7 @@ class Reader extends Vue { whatsNewVisible = false; whatsNewContent = ''; + donationVisible = false; created() { this.loading = true; @@ -258,9 +306,10 @@ class Reader extends Vue { this.checkActivateDonateHelpPage(); this.loading = false; - await this.showWhatsNew(); - this.updateRoute(); + + await this.showWhatsNew(); + await this.showDonation(); })(); } @@ -272,6 +321,7 @@ class Reader extends Vue { this.clickControl = settings.clickControl; this.blinkCachedLoad = settings.blinkCachedLoad; this.showWhatsNewDialog = settings.showWhatsNewDialog; + this.showDonationDialog2020 = settings.showDonationDialog2020; this.showToolButton = settings.showToolButton; this.enableSitesFilter = settings.enableSitesFilter; @@ -337,6 +387,41 @@ class Reader extends Vue { } } + async showDonation() { + await utils.sleep(3000); + const today = utils.formatDate(new Date(), 'coDate'); + + if (this.mode == 'omnireader' && today < '2020-03-01' && this.showDonationDialog2020 && this.donationRemindDate != today) { + this.donationVisible = true; + } + } + + donationDialogDisable() { + this.donationVisible = false; + if (this.showDonationDialog2020) { + const newSettings = Object.assign({}, this.settings, { showDonationDialog2020: false }); + this.commit('reader/setSettings', newSettings); + } + } + + donationDialogRemind() { + this.donationVisible = false; + this.commit('reader/setDonationRemindDate', utils.formatDate(new Date(), 'coDate')); + } + + openDonate() { + this.donationVisible = false; + this.donateToggle(); + } + + async copyLink(link) { + const result = await utils.copyTextToClipboard(link); + if (result) + this.$notify.success({message: `Ссылка ${link} успешно скопирована в буфер обмена`}); + else + this.$notify.error({message: 'Копирование не удалось'}); + } + openVersionHistory() { this.whatsNewVisible = false; this.versionHistoryToggle(); @@ -455,6 +540,10 @@ class Reader extends Vue { return this.$store.state.reader.whatsNewContentHash; } + get donationRemindDate() { + return this.$store.state.reader.donationRemindDate; + } + addAction(pos) { let a = this.actionList; if (!a.length || a[a.length - 1] != pos) { diff --git a/client/components/Reader/SettingsPage/SettingsPage.vue b/client/components/Reader/SettingsPage/SettingsPage.vue index d7e736c2..3f5a4b0c 100644 --- a/client/components/Reader/SettingsPage/SettingsPage.vue +++ b/client/components/Reader/SettingsPage/SettingsPage.vue @@ -471,6 +471,14 @@ Показывать уведомление "Что нового" + + + + Показывать "Оплатим хостинг вместе" + + diff --git a/client/components/Reader/versionHistory.js b/client/components/Reader/versionHistory.js index b05fa59d..98d1ced4 100644 --- a/client/components/Reader/versionHistory.js +++ b/client/components/Reader/versionHistory.js @@ -1,4 +1,16 @@ export const versionHistory = [ +{ + showUntil: '2020-01-27', + header: '0.8.3 (2020-01-28)', + content: +` + +` +}, + { showUntil: '2020-01-19', header: '0.8.2 (2020-01-20)', diff --git a/client/element.js b/client/element.js index 370ab1b4..39148440 100644 --- a/client/element.js +++ b/client/element.js @@ -19,6 +19,7 @@ import ElCheckbox from 'element-ui/lib/checkbox'; import ElTabs from 'element-ui/lib/tabs'; import ElTabPane from 'element-ui/lib/tab-pane'; import ElTooltip from 'element-ui/lib/tooltip'; +import ElRow from 'element-ui/lib/row'; import ElCol from 'element-ui/lib/col'; import ElContainer from 'element-ui/lib/container'; import ElAside from 'element-ui/lib/aside'; @@ -43,7 +44,7 @@ import MessageBox from 'element-ui/lib/message-box'; const components = { ElMenu, ElMenuItem, ElButton, ElButtonGroup, ElCheckbox, ElTabs, ElTabPane, ElTooltip, - ElCol, ElContainer, ElAside, ElMain, ElHeader, + ElRow, ElCol, ElContainer, ElAside, ElMain, ElHeader, ElInput, ElInputNumber, ElSelect, ElOption, ElTable, ElTableColumn, ElProgress, ElSlider, ElForm, ElFormItem, ElColorPicker, ElDialog, diff --git a/client/store/modules/reader.js b/client/store/modules/reader.js index 7e25c9c4..814b0984 100644 --- a/client/store/modules/reader.js +++ b/client/store/modules/reader.js @@ -182,6 +182,7 @@ const settingDefaults = { imageFitWidth: true, showServerStorageMessages: true, showWhatsNewDialog: true, + showDonationDialog2020: true, enableSitesFilter: true, fontShifts: {}, @@ -204,6 +205,7 @@ const state = { profilesRev: 0, allowProfilesSave: false,//подстраховка для разработки whatsNewContentHash: '', + donationRemindDate: '', currentProfile: '', settings: Object.assign({}, settingDefaults), settingsRev: {}, @@ -238,6 +240,9 @@ const mutations = { setWhatsNewContentHash(state, value) { state.whatsNewContentHash = value; }, + setDonationRemindDate(state, value) { + state.donationRemindDate = value; + }, setCurrentProfile(state, value) { state.currentProfile = value; }, diff --git a/package.json b/package.json index 359d8191..bddedc8e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Liberama", - "version": "0.8.2", + "version": "0.8.3", "author": "Book Pauk ", "license": "CC0-1.0", "repository": "bookpauk/liberama", diff --git a/server/core/FileDecompressor.js b/server/core/FileDecompressor.js index 6460cf46..cc7a4c0f 100644 --- a/server/core/FileDecompressor.js +++ b/server/core/FileDecompressor.js @@ -10,8 +10,9 @@ const utils = require('./utils'); const FileDetector = require('./FileDetector'); class FileDecompressor { - constructor() { + constructor(limitFileSize = 0) { this.detector = new FileDetector(); + this.limitFileSize = limitFileSize; } async decompressNested(filename, outputDir) { @@ -113,7 +114,7 @@ class FileDecompressor { async unZip(filename, outputDir) { const zip = new ZipStreamer(); - return await zip.unpack(filename, outputDir); + return await zip.unpack(filename, outputDir, null, this.limitFileSize); } unBz2(filename, outputDir) { @@ -125,9 +126,16 @@ class FileDecompressor { } unTar(filename, outputDir) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { (async() => { const files = []; + if (this.limitFileSize) { + if ((await fs.stat(filename)).size > this.limitFileSize) { + reject('Файл слишком большой'); + return; + } + } + const tarExtract = tar.extract(outputDir, { map: (header) => { files.push({path: header.name, size: header.size}); @@ -149,7 +157,7 @@ class FileDecompressor { }); inputStream.pipe(tarExtract); - }); + })().catch(reject); }); } decompressByStream(stream, filename, outputDir) { @@ -174,6 +182,16 @@ class FileDecompressor { }); stream.on('error', reject); + + if (this.limitFileSize) { + let readSize = 0; + stream.on('data', (buffer) => { + readSize += buffer.length; + if (readSize > this.limitFileSize) + stream.destroy(new Error('Файл слишком большой')); + }); + } + inputStream.on('error', reject); outputStream.on('error', reject); diff --git a/server/core/FileDownloader.js b/server/core/FileDownloader.js index 78c0df3a..018acaf4 100644 --- a/server/core/FileDownloader.js +++ b/server/core/FileDownloader.js @@ -1,12 +1,11 @@ const got = require('got'); -const maxDownloadSize = 50*1024*1024; - class FileDownloader { - constructor() { + constructor(limitDownloadSize = 0) { + this.limitDownloadSize = limitDownloadSize; } - async load(url, callback) { + async load(url, callback, abort) { let errMes = ''; const options = { encoding: null, @@ -23,10 +22,14 @@ class FileDownloader { } let prevProg = 0; - const request = got(url, options).on('downloadProgress', progress => { - if (progress.transferred > maxDownloadSize) { - errMes = 'file too big'; - request.cancel(); + const request = got(url, options); + + request.on('downloadProgress', progress => { + if (this.limitDownloadSize) { + if (progress.transferred > this.limitDownloadSize) { + errMes = 'Файл слишком большой'; + request.cancel(); + } } let prog = 0; @@ -38,8 +41,12 @@ class FileDownloader { if (prog != prevProg && callback) callback(prog); prevProg = prog; - }); + if (abort && abort()) { + errMes = 'abort'; + request.cancel(); + } + }); try { return (await request).body; diff --git a/server/core/LimitedQueue.js b/server/core/LimitedQueue.js new file mode 100644 index 00000000..393a53a8 --- /dev/null +++ b/server/core/LimitedQueue.js @@ -0,0 +1,119 @@ +class LimitedQueue { + constructor(enqueueAfter = 10, size = 100, timeout = 60*60*1000) {//timeout в ms + this.size = size; + this.timeout = timeout; + + this.abortCount = 0; + this.enqueueAfter = enqueueAfter; + this.freed = enqueueAfter; + this.listeners = []; + } + + _addListener(listener) { + this.listeners.push(listener); + } + + //отсылаем сообщение первому ожидающему и удаляем его из списка + _emitFree() { + if (this.listeners.length > 0) { + let listener = this.listeners.shift(); + listener.onFree(); + + for (let i = 0; i < this.listeners.length; i++) { + this.listeners[i].onPlaceChange(i + 1); + } + } + } + + get(onPlaceChange) { + return new Promise((resolve, reject) => { + if (this.destroyed) + reject('destroyed'); + + const take = () => { + if (this.freed <= 0) + throw new Error('Ошибка получения ресурсов в очереди ожидания'); + + this.freed--; + this.resetTimeout(); + + let aCount = this.abortCount; + return { + ret: () => { + if (aCount == this.abortCount) { + this.freed++; + this._emitFree(); + aCount = -1; + this.resetTimeout(); + } + }, + abort: () => { + return (aCount != this.abortCount); + }, + resetTimeout: this.resetTimeout.bind(this) + }; + }; + + if (this.freed > 0) { + resolve(take()); + } else { + if (this.listeners.length < this.size) { + this._addListener({ + onFree: () => { + resolve(take()); + }, + onError: (err) => { + reject(err); + }, + onPlaceChange: (i) => { + if (onPlaceChange) + onPlaceChange(i); + } + }); + if (onPlaceChange) + onPlaceChange(this.listeners.length); + } else { + reject('Превышен размер очереди ожидания'); + } + } + }); + } + + resetTimeout() { + if (this.timer) + clearTimeout(this.timer); + this.timer = setTimeout(() => { this.clean(); }, this.timeout); + } + + clean() { + this.timer = null; + + if (this.freed < this.enqueueAfter) { + this.abortCount++; + //чистка listeners + for (const listener of this.listeners) { + listener.onError('Время ожидания в очереди истекло'); + } + this.listeners = []; + + this.freed = this.enqueueAfter; + } + } + + destroy() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + + for (const listener of this.listeners) { + listener.onError('destroy'); + } + this.listeners = []; + this.abortCount++; + + this.destroyed = true; + } +} + +module.exports = LimitedQueue; \ No newline at end of file diff --git a/server/core/Reader/BookConverter/ConvertBase.js b/server/core/Reader/BookConverter/ConvertBase.js index d3ad74ce..11d07900 100644 --- a/server/core/Reader/BookConverter/ConvertBase.js +++ b/server/core/Reader/BookConverter/ConvertBase.js @@ -3,10 +3,11 @@ const iconv = require('iconv-lite'); const chardet = require('chardet'); const he = require('he'); +const LimitedQueue = require('../../LimitedQueue'); const textUtils = require('./textUtils'); const utils = require('../../utils'); -let execConverterCounter = 0; +const queue = new LimitedQueue(2, 20, 3*60*1000);//3 минуты ожидание подвижек class ConvertBase { constructor(config) { @@ -32,13 +33,16 @@ class ConvertBase { throw new Error('Внешний конвертер pdftohtml не найден'); } - async execConverter(path, args, onData) { - execConverterCounter++; + async execConverter(path, args, onData, abort) { + let q = null; try { - if (execConverterCounter > 10) - throw new Error('Слишком большая очередь конвертирования. Пожалуйста, попробуйте позже.'); + q = await queue.get(() => {onData();}); + } catch (e) { + throw new Error('Слишком большая очередь конвертирования. Пожалуйста, попробуйте позже.'); + } - const result = await utils.spawnProcess(path, {args, onData}); + try { + const result = await utils.spawnProcess(path, {args, onData, abort}); if (result.code != 0) { let error = result.code; if (this.config.branch == 'development') @@ -48,13 +52,15 @@ class ConvertBase { } 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 { - execConverterCounter--; + q.ret(); } } diff --git a/server/core/Reader/BookConverter/ConvertDoc.js b/server/core/Reader/BookConverter/ConvertDoc.js index ce627eb8..a0c24191 100644 --- a/server/core/Reader/BookConverter/ConvertDoc.js +++ b/server/core/Reader/BookConverter/ConvertDoc.js @@ -16,7 +16,7 @@ class ConvertDoc extends ConvertDocX { return false; await this.checkExternalConverterPresent(); - const {inputFiles, callback} = opts; + const {inputFiles, callback, abort} = opts; const outFile = `${inputFiles.filesDir}/${path.basename(inputFiles.sourceFile)}`; const docFile = `${outFile}.doc`; @@ -24,9 +24,9 @@ class ConvertDoc extends ConvertDocX { const fb2File = `${outFile}.fb2`; await fs.copy(inputFiles.sourceFile, docFile); - await this.execConverter(this.sofficePath, ['--headless', '--convert-to', 'docx', '--outdir', inputFiles.filesDir, docFile]); + await this.execConverter(this.sofficePath, ['--headless', '--convert-to', 'docx', '--outdir', inputFiles.filesDir, docFile], null, abort); - return await super.convert(docxFile, fb2File, callback); + return await super.convert(docxFile, fb2File, callback, abort); } } diff --git a/server/core/Reader/BookConverter/ConvertDocX.js b/server/core/Reader/BookConverter/ConvertDocX.js index d8c85b78..02b112c5 100644 --- a/server/core/Reader/BookConverter/ConvertDocX.js +++ b/server/core/Reader/BookConverter/ConvertDocX.js @@ -20,12 +20,12 @@ class ConvertDocX extends ConvertBase { return false; } - async convert(docxFile, fb2File, callback) { + async convert(docxFile, fb2File, callback, abort) { let perc = 0; await this.execConverter(this.calibrePath, [docxFile, fb2File], () => { perc = (perc < 100 ? perc + 5 : 50); callback(perc); - }); + }, abort); return await fs.readFile(fb2File); } @@ -35,7 +35,7 @@ class ConvertDocX extends ConvertBase { return false; await this.checkExternalConverterPresent(); - const {inputFiles, callback} = opts; + const {inputFiles, callback, abort} = opts; const outFile = `${inputFiles.filesDir}/${path.basename(inputFiles.sourceFile)}`; const docxFile = `${outFile}.docx`; @@ -43,7 +43,7 @@ class ConvertDocX extends ConvertBase { await fs.copy(inputFiles.sourceFile, docxFile); - return await this.convert(docxFile, fb2File, callback); + return await this.convert(docxFile, fb2File, callback, abort); } } diff --git a/server/core/Reader/BookConverter/ConvertEpub.js b/server/core/Reader/BookConverter/ConvertEpub.js index 79b25b8e..f0f9ba71 100644 --- a/server/core/Reader/BookConverter/ConvertEpub.js +++ b/server/core/Reader/BookConverter/ConvertEpub.js @@ -28,7 +28,7 @@ class ConvertEpub extends ConvertBase { return false; await this.checkExternalConverterPresent(); - const {inputFiles, callback} = opts; + const {inputFiles, callback, abort} = opts; const outFile = `${inputFiles.filesDir}/${path.basename(inputFiles.sourceFile)}`; const epubFile = `${outFile}.epub`; @@ -40,7 +40,7 @@ class ConvertEpub extends ConvertBase { await this.execConverter(this.calibrePath, [epubFile, fb2File], () => { perc = (perc < 100 ? perc + 5 : 50); callback(perc); - }); + }, abort); return await fs.readFile(fb2File); } diff --git a/server/core/Reader/BookConverter/ConvertMobi.js b/server/core/Reader/BookConverter/ConvertMobi.js index e8d6172b..ee8535ef 100644 --- a/server/core/Reader/BookConverter/ConvertMobi.js +++ b/server/core/Reader/BookConverter/ConvertMobi.js @@ -16,7 +16,7 @@ class ConvertMobi extends ConvertBase { return false; await this.checkExternalConverterPresent(); - const {inputFiles, callback} = opts; + const {inputFiles, callback, abort} = opts; const outFile = `${inputFiles.filesDir}/${path.basename(inputFiles.sourceFile)}`; const mobiFile = `${outFile}.mobi`; @@ -28,7 +28,7 @@ class ConvertMobi extends ConvertBase { await this.execConverter(this.calibrePath, [mobiFile, fb2File], () => { perc = (perc < 100 ? perc + 5 : 50); callback(perc); - }); + }, abort); return await fs.readFile(fb2File); } diff --git a/server/core/Reader/BookConverter/ConvertPdf.js b/server/core/Reader/BookConverter/ConvertPdf.js index be885369..397a1868 100644 --- a/server/core/Reader/BookConverter/ConvertPdf.js +++ b/server/core/Reader/BookConverter/ConvertPdf.js @@ -18,7 +18,7 @@ class ConvertPdf extends ConvertHtml { return false; await this.checkExternalConverterPresent(); - const {inputFiles, callback} = opts; + const {inputFiles, callback, abort} = opts; const outFile = `${inputFiles.filesDir}/${utils.randomHexString(10)}.xml`; @@ -27,7 +27,7 @@ class ConvertPdf extends ConvertHtml { await this.execConverter(this.pdfToHtmlPath, ['-c', '-s', '-xml', inputFiles.sourceFile, outFile], () => { perc = (perc < 80 ? perc + 10 : 40); callback(perc); - }); + }, abort); callback(80); const data = await fs.readFile(outFile); diff --git a/server/core/Reader/BookConverter/ConvertRtf.js b/server/core/Reader/BookConverter/ConvertRtf.js index 61f489f7..5f167cd6 100644 --- a/server/core/Reader/BookConverter/ConvertRtf.js +++ b/server/core/Reader/BookConverter/ConvertRtf.js @@ -16,7 +16,7 @@ class ConvertRtf extends ConvertDocX { return false; await this.checkExternalConverterPresent(); - const {inputFiles, callback} = opts; + const {inputFiles, callback, abort} = opts; const outFile = `${inputFiles.filesDir}/${path.basename(inputFiles.sourceFile)}`; const rtfFile = `${outFile}.rtf`; @@ -24,9 +24,9 @@ class ConvertRtf extends ConvertDocX { const fb2File = `${outFile}.fb2`; await fs.copy(inputFiles.sourceFile, rtfFile); - await this.execConverter(this.sofficePath, ['--headless', '--convert-to', 'docx', '--outdir', inputFiles.filesDir, rtfFile]); + await this.execConverter(this.sofficePath, ['--headless', '--convert-to', 'docx', '--outdir', inputFiles.filesDir, rtfFile], null, abort); - return await super.convert(docxFile, fb2File, callback); + return await super.convert(docxFile, fb2File, callback, abort); } } diff --git a/server/core/Reader/BookConverter/index.js b/server/core/Reader/BookConverter/index.js index 89b73f5a..d91de21e 100644 --- a/server/core/Reader/BookConverter/index.js +++ b/server/core/Reader/BookConverter/index.js @@ -26,11 +26,14 @@ class BookConverter { } } - async convertToFb2(inputFiles, outputFile, opts, callback) { + async convertToFb2(inputFiles, outputFile, opts, callback, abort) { + if (abort && abort()) + throw new Error('abort'); + const selectedFileType = await this.detector.detectFile(inputFiles.selectedFile); const data = await fs.readFile(inputFiles.selectedFile); - const convertOpts = Object.assign({}, opts, {inputFiles, callback, dataType: selectedFileType}); + const convertOpts = Object.assign({}, opts, {inputFiles, callback, abort, dataType: selectedFileType}); let result = false; for (const convert of this.convertFactory) { result = await convert.run(data, convertOpts); @@ -41,7 +44,7 @@ class BookConverter { } if (!result && inputFiles.nesting) { - result = await this.convertToFb2(inputFiles.nesting, outputFile, opts, callback); + result = await this.convertToFb2(inputFiles.nesting, outputFile, opts, callback, abort); } if (!result) { diff --git a/server/core/Reader/ReaderWorker.js b/server/core/Reader/ReaderWorker.js index ab407406..dafc628a 100644 --- a/server/core/Reader/ReaderWorker.js +++ b/server/core/Reader/ReaderWorker.js @@ -1,6 +1,7 @@ const fs = require('fs-extra'); const path = require('path'); +const LimitedQueue = require('../LimitedQueue'); const WorkerState = require('../WorkerState');//singleton const FileDownloader = require('../FileDownloader'); const FileDecompressor = require('../FileDecompressor'); @@ -11,6 +12,7 @@ const utils = require('../utils'); const log = new (require('../AppLogger'))().log;//singleton const cleanDirPeriod = 60*60*1000;//1 раз в час +const queue = new LimitedQueue(5, 100, 5*60*1000);//5 минут ожидание подвижек let instance = null; @@ -27,8 +29,8 @@ class ReaderWorker { fs.ensureDirSync(this.config.tempPublicDir); this.workerState = new WorkerState(); - this.down = new FileDownloader(); - this.decomp = new FileDecompressor(); + this.down = new FileDownloader(config.maxUploadFileSize); + this.decomp = new FileDecompressor(2*config.maxUploadFileSize); this.bookConverter = new BookConverter(this.config); this.remoteWebDavStorage = false; @@ -53,17 +55,35 @@ class ReaderWorker { let downloadedFilename = ''; let isUploaded = false; let convertFilename = ''; + + const overLoadMes = 'Слишком большая очередь загрузки. Пожалуйста, попробуйте позже.'; + const overLoadErr = new Error(overLoadMes); + + let q = null; try { + wState.set({state: 'queue', step: 1, totalSteps: 1}); + try { + let qSize = 0; + q = await queue.get((place) => { + wState.set({place, progress: (qSize ? Math.round((qSize - place)/qSize*100) : 0)}); + if (!qSize) + qSize = place; + }); + } catch (e) { + throw overLoadErr; + } + wState.set({state: 'download', step: 1, totalSteps: 3, url}); const tempFilename = utils.randomHexString(30); const tempFilename2 = utils.randomHexString(30); const decompDirname = utils.randomHexString(30); + //download or use uploaded if (url.indexOf('file://') != 0) {//download const downdata = await this.down.load(url, (progress) => { wState.set({progress}); - }); + }, q.abort); downloadedFilename = `${this.config.tempDownloadDir}/${tempFilename}`; await fs.writeFile(downloadedFilename, downdata); @@ -76,6 +96,10 @@ class ReaderWorker { } wState.set({progress: 100}); + if (q.abort()) + throw overLoadErr; + q.resetTimeout(); + //decompress wState.set({state: 'decompress', step: 2, progress: 0}); decompDir = `${this.config.tempDownloadDir}/${decompDirname}`; @@ -88,12 +112,16 @@ class ReaderWorker { } wState.set({progress: 100}); + if (q.abort()) + throw overLoadErr; + q.resetTimeout(); + //конвертирование в fb2 wState.set({state: 'convert', step: 3, progress: 0}); convertFilename = `${this.config.tempDownloadDir}/${tempFilename2}`; await this.bookConverter.convertToFb2(decompFiles, convertFilename, opts, progress => { wState.set({progress}); - }); + }, q.abort); //сжимаем файл в tmp, если там уже нет с тем же именем-sha256 const compFilename = await this.decomp.gzipFileIfNotExists(convertFilename, this.config.tempPublicDir); @@ -120,9 +148,13 @@ class ReaderWorker { } catch (e) { log(LM_ERR, e.stack); + if (e.message == 'abort') + e.message = overLoadMes; wState.set({state: 'error', error: e.message}); } finally { //clean + if (q) + q.ret(); if (decompDir) await fs.remove(decompDir); if (downloadedFilename && !isUploaded) diff --git a/server/core/ZipStreamer.js b/server/core/ZipStreamer.js index 03f59ff5..c761eaa9 100644 --- a/server/core/ZipStreamer.js +++ b/server/core/ZipStreamer.js @@ -52,7 +52,7 @@ class ZipStreamer { })().catch(reject); }); } - unpack(zipFile, outputDir, entryCallback) { + unpack(zipFile, outputDir, entryCallback, limitFileSize = 0) { return new Promise((resolve, reject) => { entryCallback = (entryCallback ? entryCallback : () => {}); const unzip = new unzipStream({file: zipFile}); @@ -67,6 +67,15 @@ class ZipStreamer { }); unzip.on('ready', () => { + if (limitFileSize) { + for (const entry of Object.values(unzip.entries())) { + if (!entry.isDirectory && entry.size > limitFileSize) { + reject('Файл слишком большой'); + return; + } + } + } + unzip.extract(null, outputDir, (err) => { if (err) reject(err); unzip.close(); diff --git a/server/core/utils.js b/server/core/utils.js index 9591c7d7..53f7a2b3 100644 --- a/server/core/utils.js +++ b/server/core/utils.js @@ -37,8 +37,8 @@ async function touchFile(filename) { } function spawnProcess(cmd, opts) { - let {args, killAfter, onData} = opts; - killAfter = (killAfter ? killAfter : 120*1000); + let {args, killAfter, onData, abort} = opts; + killAfter = (killAfter ? killAfter : 120);//seconds onData = (onData ? onData : () => {}); args = (args ? args : []); @@ -67,10 +67,18 @@ function spawnProcess(cmd, opts) { reject({status: 'error', error, stdout, stderr}); }); - await sleep(killAfter); - if (!resolved) { - process.kill(proc.pid); - reject({status: 'killed', stdout, stderr}); + while (!resolved) { + await sleep(1000); + killAfter -= 1; + if (killAfter <= 0 || (abort && abort())) { + process.kill(proc.pid); + if (killAfter <= 0) { + reject({status: 'killed', stdout, stderr}); + } else { + reject({status: 'abort', stdout, stderr}); + } + break; + } } })().catch(reject); }); } diff --git a/server/db/SqliteConnectionPool.js b/server/db/SqliteConnectionPool.js index a1acb919..a5189a9b 100644 --- a/server/db/SqliteConnectionPool.js +++ b/server/db/SqliteConnectionPool.js @@ -14,7 +14,6 @@ class SqliteConnectionPool { if (!Number.isInteger(connCount) || connCount <= 0) return; this.connections = []; - this.taken = new Set(); this.freed = new Set(); for (let i = 0; i < connCount; i++) { @@ -22,7 +21,6 @@ class SqliteConnectionPool { client.configure('busyTimeout', 10000); //ms client.ret = () => { - this.taken.delete(i); this.freed.add(i); }; @@ -52,7 +50,6 @@ class SqliteConnectionPool { } this.freed.delete(freeConnIndex); - this.taken.add(freeConnIndex); return this.connections[freeConnIndex]; }