From 2569d00bd0f8bee2ce3d082bf365b87c0daaef6f Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 26 Jan 2020 13:47:25 +0700 Subject: [PATCH 01/16] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/api/reader.js | 6 ++++++ 1 file changed, 6 insertions(+) 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; } From 7997c486cf406dea4dd31757c99703f84d33bcf1 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 26 Jan 2020 15:07:14 +0700 Subject: [PATCH 02/16] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B9=20?= =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/core/FileDownloader.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/server/core/FileDownloader.js b/server/core/FileDownloader.js index 78c0df3a..d6c53897 100644 --- a/server/core/FileDownloader.js +++ b/server/core/FileDownloader.js @@ -1,9 +1,8 @@ const got = require('got'); -const maxDownloadSize = 50*1024*1024; - class FileDownloader { - constructor() { + constructor(limitDownloadSize = 0) { + this.limitDownloadSize = limitDownloadSize; } async load(url, callback) { @@ -24,9 +23,11 @@ class FileDownloader { let prevProg = 0; const request = got(url, options).on('downloadProgress', progress => { - if (progress.transferred > maxDownloadSize) { - errMes = 'file too big'; - request.cancel(); + if (this.limitDownloadSize) { + if (progress.transferred > this.limitDownloadSize) { + errMes = 'Файл слишком большой'; + request.cancel(); + } } let prog = 0; From 639f726c837675bd21eedef07e3175251260ac8f Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 26 Jan 2020 15:17:45 +0700 Subject: [PATCH 03/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BB=D0=B8=D0=BC=D0=B8=D1=82=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=20=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=80=D0=B8=20=D1=80=D0=B0=D1=81=D0=BF=D0=B0?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/core/FileDecompressor.js | 26 ++++++++++++++++++++++---- server/core/Reader/ReaderWorker.js | 4 ++-- server/core/ZipStreamer.js | 11 ++++++++++- 3 files changed, 34 insertions(+), 7 deletions(-) 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/Reader/ReaderWorker.js b/server/core/Reader/ReaderWorker.js index ab407406..74c7eb2c 100644 --- a/server/core/Reader/ReaderWorker.js +++ b/server/core/Reader/ReaderWorker.js @@ -27,8 +27,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; 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(); From 4b9475310fc1e9a12fc834877de89fec1845e572 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 26 Jan 2020 16:23:20 +0700 Subject: [PATCH 04/16] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=83=D0=B6=D0=BD=D1=8B=D0=B9=20this.taken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/db/SqliteConnectionPool.js | 3 --- 1 file changed, 3 deletions(-) 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]; } From b7568975e78672120ca22e75515cce23cc8508a0 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 26 Jan 2020 18:31:31 +0700 Subject: [PATCH 05/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20state=20=3D=20'queue'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/components/Reader/ProgressPage/ProgressPage.vue | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/components/Reader/ProgressPage/ProgressPage.vue b/client/components/Reader/ProgressPage/ProgressPage.vue index 0ac1a0bc..49b31c29 100644 --- a/client/components/Reader/ProgressPage/ProgressPage.vue +++ b/client/components/Reader/ProgressPage/ProgressPage.vue @@ -16,6 +16,7 @@ const ruMessage = { 'start': ' ', 'finish': ' ', 'error': ' ', + 'queue': 'очередь', 'download': 'скачивание', 'decompress': 'распаковка', 'convert': 'конвертирование', @@ -49,8 +50,13 @@ class ProgressPage extends Vue { } setState(state) { - if (state.state) - this.text = (ruMessage[state.state] ? ruMessage[state.state] : state.state); + if (state.state) { + if (state.state == 'queue') { + this.text = 'Номер в очереди: ' + (state.place ? state.place : ''); + } else { + this.text = (ruMessage[state.state] ? ruMessage[state.state] : state.state); + } + } this.step = (state.step ? state.step : this.step); this.totalSteps = (state.totalSteps > this.totalSteps ? state.totalSteps : this.totalSteps); this.progress = state.progress || 0; From a50d61c3ce320e97bcdd7ba49f686f2ed6a23810 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 26 Jan 2020 18:37:14 +0700 Subject: [PATCH 06/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=87=D0=B5=D1=80=D0=B5=D0=B4=D1=8C?= =?UTF-8?q?=20=D1=81=D0=BA=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B8=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5=D1=80=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/Reader/BookConverter/ConvertBase.js | 15 +++++++++------ server/core/Reader/ReaderWorker.js | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/server/core/Reader/BookConverter/ConvertBase.js b/server/core/Reader/BookConverter/ConvertBase.js index d3ad74ce..bf24af84 100644 --- a/server/core/Reader/BookConverter/ConvertBase.js +++ b/server/core/Reader/BookConverter/ConvertBase.js @@ -3,11 +3,10 @@ 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; - class ConvertBase { constructor(config) { this.config = config; @@ -15,6 +14,7 @@ class ConvertBase { this.calibrePath = `${config.dataDir}/calibre/ebook-convert`; this.sofficePath = '/usr/bin/soffice'; this.pdfToHtmlPath = '/usr/bin/pdftohtml'; + this.queue = new LimitedQueue(2, 20, 3); } async run(data, opts) {// eslint-disable-line no-unused-vars @@ -33,11 +33,14 @@ class ConvertBase { } async execConverter(path, args, onData) { - execConverterCounter++; + let q = null; try { - if (execConverterCounter > 10) - throw new Error('Слишком большая очередь конвертирования. Пожалуйста, попробуйте позже.'); + q = await this.queue.get(() => {onData();}); + } catch (e) { + throw new Error('Слишком большая очередь конвертирования. Пожалуйста, попробуйте позже.'); + } + try { const result = await utils.spawnProcess(path, {args, onData}); if (result.code != 0) { let error = result.code; @@ -54,7 +57,7 @@ class ConvertBase { throw new Error(e); } } finally { - execConverterCounter--; + q.ret(); } } diff --git a/server/core/Reader/ReaderWorker.js b/server/core/Reader/ReaderWorker.js index 74c7eb2c..7507887c 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'); @@ -26,6 +27,7 @@ class ReaderWorker { this.config.tempPublicDir = `${config.publicDir}/tmp`; fs.ensureDirSync(this.config.tempPublicDir); + this.queue = new LimitedQueue(5, 100, 3); this.workerState = new WorkerState(); this.down = new FileDownloader(config.maxUploadFileSize); this.decomp = new FileDecompressor(2*config.maxUploadFileSize); @@ -53,7 +55,21 @@ class ReaderWorker { let downloadedFilename = ''; let isUploaded = false; let convertFilename = ''; + + let q = null; try { + wState.set({state: 'queue', step: 1, totalSteps: 1}); + try { + let qSize = 0; + q = await this.queue.get((place) => { + wState.set({place, progress: (qSize ? Math.round((qSize - place)/qSize*100) : 0)}); + if (!qSize) + qSize = place; + }); + } catch (e) { + throw new Error('Слишком большая очередь загрузки. Пожалуйста, попробуйте позже.'); + } + wState.set({state: 'download', step: 1, totalSteps: 3, url}); const tempFilename = utils.randomHexString(30); @@ -123,6 +139,8 @@ class ReaderWorker { wState.set({state: 'error', error: e.message}); } finally { //clean + if (q) + q.ret(); if (decompDir) await fs.remove(decompDir); if (downloadedFilename && !isUploaded) From 3da6befe1058816f122e269f7e37bde9a83c970b Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 26 Jan 2020 18:38:09 +0700 Subject: [PATCH 07/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20LimitedQueue=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=80=D0=B3=D0=B0=D0=BD=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BE=D1=87=D0=B5=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/core/LimitedQueue.js | 120 ++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 server/core/LimitedQueue.js diff --git a/server/core/LimitedQueue.js b/server/core/LimitedQueue.js new file mode 100644 index 00000000..fd9d68e0 --- /dev/null +++ b/server/core/LimitedQueue.js @@ -0,0 +1,120 @@ +const cleanPeriod = 60*1000;//1 минута +const cleanTimeout = 60;//timeout в минутах (cleanPeriod) + +class LimitedQueue { + constructor(enqueueAfter = 10, size = 100, timeout = cleanTimeout) {//timeout в минутах (cleanPeriod) + this.size = size; + this.timeout = timeout; + + this.freed = enqueueAfter; + this.listeners = []; + + this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod); + } + + _addListener(listener) { + this.listeners.push(Object.assign({regTime: Date.now()}, listener)); + } + + //отсылаем сообщение первому ожидающему и удаляем его из списка + _emitFree() { + if (this.listeners.length > 0) { + let listener = this.listeners.shift(); + listener.onFree(); + + const now = Date.now(); + for (let i = 0; i < this.listeners.length; i++) { + listener = this.listeners[i]; + listener.regTime = now; + listener.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--; + + let returned = false; + return { + ret: () => { + if (!returned) { + this.freed++; + this._emitFree(); + returned = true; + } + } + }; + }; + + 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('Превышен размер очереди ожидания'); + } + } + }); + } + + destroy() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + + for (const listener of this.listeners) { + listener.onError('destroy'); + } + this.listeners = []; + + this.destroyed = true; + } + + periodicClean() { + try { + this.timer = null; + + const now = Date.now(); + //чистка listeners, убираем зависшие в очереди на одном месте + let newListeners = []; + for (const listener of this.listeners) { + if (now - listener.regTime < this.timeout*cleanPeriod - 50) { + newListeners.push(listener); + } else { + listener.onError('Время ожидания в очереди истекло'); + } + } + this.listeners = newListeners; + } finally { + if (!this.destroyed) { + this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod); + } + } + } +} + +module.exports = LimitedQueue; \ No newline at end of file From f8b7b8b6985eacf16a02a83d0275f6e5bbb9c8aa Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Mon, 27 Jan 2020 18:57:42 +0700 Subject: [PATCH 08/16] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20LimitedQueue,=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B3=D0=BE=D0=B2,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=84=D0=BB=D0=B0=D0=B3=D0=B0=20abort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/core/FileDownloader.js | 12 ++- server/core/LimitedQueue.js | 73 +++++++++---------- .../core/Reader/BookConverter/ConvertBase.js | 2 +- server/core/Reader/ReaderWorker.js | 22 +++++- 4 files changed, 64 insertions(+), 45 deletions(-) diff --git a/server/core/FileDownloader.js b/server/core/FileDownloader.js index d6c53897..018acaf4 100644 --- a/server/core/FileDownloader.js +++ b/server/core/FileDownloader.js @@ -5,7 +5,7 @@ class FileDownloader { this.limitDownloadSize = limitDownloadSize; } - async load(url, callback) { + async load(url, callback, abort) { let errMes = ''; const options = { encoding: null, @@ -22,7 +22,9 @@ class FileDownloader { } let prevProg = 0; - const request = got(url, options).on('downloadProgress', progress => { + const request = got(url, options); + + request.on('downloadProgress', progress => { if (this.limitDownloadSize) { if (progress.transferred > this.limitDownloadSize) { errMes = 'Файл слишком большой'; @@ -39,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 index fd9d68e0..122ee270 100644 --- a/server/core/LimitedQueue.js +++ b/server/core/LimitedQueue.js @@ -1,19 +1,16 @@ -const cleanPeriod = 60*1000;//1 минута -const cleanTimeout = 60;//timeout в минутах (cleanPeriod) - class LimitedQueue { - constructor(enqueueAfter = 10, size = 100, timeout = cleanTimeout) {//timeout в минутах (cleanPeriod) + 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 = []; - - this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod); } _addListener(listener) { - this.listeners.push(Object.assign({regTime: Date.now()}, listener)); + this.listeners.push(listener); } //отсылаем сообщение первому ожидающему и удаляем его из списка @@ -22,13 +19,11 @@ class LimitedQueue { let listener = this.listeners.shift(); listener.onFree(); - const now = Date.now(); for (let i = 0; i < this.listeners.length; i++) { - listener = this.listeners[i]; - listener.regTime = now; - listener.onPlaceChange(i + 1); + this.listeners[i].onPlaceChange(i + 1); } + this.resetTimeout(); } } @@ -42,16 +37,21 @@ class LimitedQueue { throw new Error('Ошибка получения ресурсов в очереди ожидания'); this.freed--; + this.resetTimeout(); - let returned = false; + let aCount = this.abortCount; return { ret: () => { - if (!returned) { + if (aCount == this.abortCount) { this.freed++; this._emitFree(); - returned = true; + aCount = -1; } - } + }, + abort: () => { + return (aCount != this.abortCount); + }, + resetTimeout: this.resetTimeout.bind(this) }; }; @@ -80,6 +80,27 @@ class LimitedQueue { }); } + 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); @@ -93,28 +114,6 @@ class LimitedQueue { this.destroyed = true; } - - periodicClean() { - try { - this.timer = null; - - const now = Date.now(); - //чистка listeners, убираем зависшие в очереди на одном месте - let newListeners = []; - for (const listener of this.listeners) { - if (now - listener.regTime < this.timeout*cleanPeriod - 50) { - newListeners.push(listener); - } else { - listener.onError('Время ожидания в очереди истекло'); - } - } - this.listeners = newListeners; - } finally { - if (!this.destroyed) { - this.timer = setTimeout(() => { this.periodicClean(); }, cleanPeriod); - } - } - } } 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 bf24af84..70a1aa7d 100644 --- a/server/core/Reader/BookConverter/ConvertBase.js +++ b/server/core/Reader/BookConverter/ConvertBase.js @@ -14,7 +14,7 @@ class ConvertBase { this.calibrePath = `${config.dataDir}/calibre/ebook-convert`; this.sofficePath = '/usr/bin/soffice'; this.pdfToHtmlPath = '/usr/bin/pdftohtml'; - this.queue = new LimitedQueue(2, 20, 3); + this.queue = new LimitedQueue(2, 20, 3*60*1000); } async run(data, opts) {// eslint-disable-line no-unused-vars diff --git a/server/core/Reader/ReaderWorker.js b/server/core/Reader/ReaderWorker.js index 7507887c..ac04138f 100644 --- a/server/core/Reader/ReaderWorker.js +++ b/server/core/Reader/ReaderWorker.js @@ -27,7 +27,7 @@ class ReaderWorker { this.config.tempPublicDir = `${config.publicDir}/tmp`; fs.ensureDirSync(this.config.tempPublicDir); - this.queue = new LimitedQueue(5, 100, 3); + this.queue = new LimitedQueue(5, 100, 3*60*1000); this.workerState = new WorkerState(); this.down = new FileDownloader(config.maxUploadFileSize); this.decomp = new FileDecompressor(2*config.maxUploadFileSize); @@ -56,6 +56,9 @@ class ReaderWorker { let isUploaded = false; let convertFilename = ''; + const overLoadMes = 'Слишком большая очередь загрузки. Пожалуйста, попробуйте позже.'; + const overLoadErr = new Error(overLoadMes); + let q = null; try { wState.set({state: 'queue', step: 1, totalSteps: 1}); @@ -67,7 +70,7 @@ class ReaderWorker { qSize = place; }); } catch (e) { - throw new Error('Слишком большая очередь загрузки. Пожалуйста, попробуйте позже.'); + throw overLoadErr; } wState.set({state: 'download', step: 1, totalSteps: 3, url}); @@ -76,10 +79,11 @@ class ReaderWorker { 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); @@ -92,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}`; @@ -104,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); @@ -136,6 +148,8 @@ 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 From 57fc64af79f38c911d07bd23b240c38a27b5ec59 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Mon, 27 Jan 2020 19:34:10 +0700 Subject: [PATCH 09/16] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20abort=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D1=82=D0=B5=D1=80=D0=BE=D0=B2=20=D0=BF=D1=80=D0=B8=20=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=B5=D1=87=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=B2=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=BE=D0=B6=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B2=D0=B8=D0=B6=D0=B5?= =?UTF-8?q?=D0=BA=20=D0=BE=D1=87=D0=B5=D1=80=D0=B5=D0=B4=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/Reader/BookConverter/ConvertBase.js | 8 +++++--- .../core/Reader/BookConverter/ConvertDoc.js | 6 +++--- .../core/Reader/BookConverter/ConvertDocX.js | 8 ++++---- .../core/Reader/BookConverter/ConvertEpub.js | 4 ++-- .../core/Reader/BookConverter/ConvertMobi.js | 4 ++-- .../core/Reader/BookConverter/ConvertPdf.js | 4 ++-- .../core/Reader/BookConverter/ConvertRtf.js | 6 +++--- server/core/Reader/BookConverter/index.js | 9 ++++++--- server/core/Reader/ReaderWorker.js | 2 +- server/core/utils.js | 20 +++++++++++++------ 10 files changed, 42 insertions(+), 29 deletions(-) diff --git a/server/core/Reader/BookConverter/ConvertBase.js b/server/core/Reader/BookConverter/ConvertBase.js index 70a1aa7d..98074a06 100644 --- a/server/core/Reader/BookConverter/ConvertBase.js +++ b/server/core/Reader/BookConverter/ConvertBase.js @@ -14,7 +14,7 @@ class ConvertBase { this.calibrePath = `${config.dataDir}/calibre/ebook-convert`; this.sofficePath = '/usr/bin/soffice'; this.pdfToHtmlPath = '/usr/bin/pdftohtml'; - this.queue = new LimitedQueue(2, 20, 3*60*1000); + this.queue = new LimitedQueue(2, 20, 3*60*1000);//3 минуты ожидание подвижек } async run(data, opts) {// eslint-disable-line no-unused-vars @@ -32,7 +32,7 @@ class ConvertBase { throw new Error('Внешний конвертер pdftohtml не найден'); } - async execConverter(path, args, onData) { + async execConverter(path, args, onData, abort) { let q = null; try { q = await this.queue.get(() => {onData();}); @@ -41,7 +41,7 @@ class ConvertBase { } try { - const result = await utils.spawnProcess(path, {args, onData}); + const result = await utils.spawnProcess(path, {args, onData, abort}); if (result.code != 0) { let error = result.code; if (this.config.branch == 'development') @@ -51,6 +51,8 @@ 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 { 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 ac04138f..078bafca 100644 --- a/server/core/Reader/ReaderWorker.js +++ b/server/core/Reader/ReaderWorker.js @@ -27,7 +27,7 @@ class ReaderWorker { this.config.tempPublicDir = `${config.publicDir}/tmp`; fs.ensureDirSync(this.config.tempPublicDir); - this.queue = new LimitedQueue(5, 100, 3*60*1000); + this.queue = new LimitedQueue(5, 100, 5*60*1000);//5 минут ожидание подвижек this.workerState = new WorkerState(); this.down = new FileDownloader(config.maxUploadFileSize); this.decomp = new FileDecompressor(2*config.maxUploadFileSize); 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); }); } From a5cb2641fd2339675a72e84be4dd5582da8938ba Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Mon, 27 Jan 2020 19:42:30 +0700 Subject: [PATCH 10/16] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/core/LimitedQueue.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/core/LimitedQueue.js b/server/core/LimitedQueue.js index 122ee270..d0784652 100644 --- a/server/core/LimitedQueue.js +++ b/server/core/LimitedQueue.js @@ -22,8 +22,6 @@ class LimitedQueue { for (let i = 0; i < this.listeners.length; i++) { this.listeners[i].onPlaceChange(i + 1); } - - this.resetTimeout(); } } @@ -46,6 +44,7 @@ class LimitedQueue { this.freed++; this._emitFree(); aCount = -1; + this.resetTimeout(); } }, abort: () => { From 76f7d7bc905bbfe0bdf369c78b0ce8ea86b61e86 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Mon, 27 Jan 2020 19:52:56 +0700 Subject: [PATCH 11/16] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/core/LimitedQueue.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/core/LimitedQueue.js b/server/core/LimitedQueue.js index d0784652..393a53a8 100644 --- a/server/core/LimitedQueue.js +++ b/server/core/LimitedQueue.js @@ -110,6 +110,7 @@ class LimitedQueue { listener.onError('destroy'); } this.listeners = []; + this.abortCount++; this.destroyed = true; } From 1226acefd62a75ed7d2bb87c598d1b0b2d97566b Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Tue, 28 Jan 2020 14:51:09 +0700 Subject: [PATCH 12/16] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=B8=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F,=20queue=20=D1=82=D0=B5=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D1=8C=20=D0=B2=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=BC=20=D1=8D?= =?UTF-8?q?=D0=BA=D0=B7=D0=B5=D0=BC=D0=BF=D0=BB=D1=8F=D1=80=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/core/Reader/BookConverter/ConvertBase.js | 5 +++-- server/core/Reader/ReaderWorker.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server/core/Reader/BookConverter/ConvertBase.js b/server/core/Reader/BookConverter/ConvertBase.js index 98074a06..11d07900 100644 --- a/server/core/Reader/BookConverter/ConvertBase.js +++ b/server/core/Reader/BookConverter/ConvertBase.js @@ -7,6 +7,8 @@ const LimitedQueue = require('../../LimitedQueue'); const textUtils = require('./textUtils'); const utils = require('../../utils'); +const queue = new LimitedQueue(2, 20, 3*60*1000);//3 минуты ожидание подвижек + class ConvertBase { constructor(config) { this.config = config; @@ -14,7 +16,6 @@ class ConvertBase { this.calibrePath = `${config.dataDir}/calibre/ebook-convert`; this.sofficePath = '/usr/bin/soffice'; this.pdfToHtmlPath = '/usr/bin/pdftohtml'; - this.queue = new LimitedQueue(2, 20, 3*60*1000);//3 минуты ожидание подвижек } async run(data, opts) {// eslint-disable-line no-unused-vars @@ -35,7 +36,7 @@ class ConvertBase { async execConverter(path, args, onData, abort) { let q = null; try { - q = await this.queue.get(() => {onData();}); + q = await queue.get(() => {onData();}); } catch (e) { throw new Error('Слишком большая очередь конвертирования. Пожалуйста, попробуйте позже.'); } diff --git a/server/core/Reader/ReaderWorker.js b/server/core/Reader/ReaderWorker.js index 078bafca..dafc628a 100644 --- a/server/core/Reader/ReaderWorker.js +++ b/server/core/Reader/ReaderWorker.js @@ -12,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,7 +28,6 @@ class ReaderWorker { this.config.tempPublicDir = `${config.publicDir}/tmp`; fs.ensureDirSync(this.config.tempPublicDir); - this.queue = new LimitedQueue(5, 100, 5*60*1000);//5 минут ожидание подвижек this.workerState = new WorkerState(); this.down = new FileDownloader(config.maxUploadFileSize); this.decomp = new FileDecompressor(2*config.maxUploadFileSize); @@ -64,7 +64,7 @@ class ReaderWorker { wState.set({state: 'queue', step: 1, totalSteps: 1}); try { let qSize = 0; - q = await this.queue.get((place) => { + q = await queue.get((place) => { wState.set({place, progress: (qSize ? Math.round((qSize - place)/qSize*100) : 0)}); if (!qSize) qSize = place; From efb24137208a58dbe5801db5e2f3512d2d8d8fc6 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Tue, 28 Jan 2020 19:44:52 +0700 Subject: [PATCH 13/16] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=BE=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=81=D0=BE=D0=B4=D0=B5=D1=80=D0=B6=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DonateHelpPage/DonateHelpPage.vue | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) 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 +245,7 @@ class Reader extends Vue { whatsNewVisible = false; whatsNewContent = ''; + donationVisible = false; created() { this.loading = true; @@ -258,9 +304,10 @@ class Reader extends Vue { this.checkActivateDonateHelpPage(); this.loading = false; - await this.showWhatsNew(); - this.updateRoute(); + + await this.showWhatsNew(); + await this.showDonation(); })(); } @@ -272,6 +319,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 +385,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 +538,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/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; }, From 56ad41d10cc17f8c2c2fe649827f129a96a8c2b4 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Tue, 28 Jan 2020 20:18:02 +0700 Subject: [PATCH 15/16] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D1=82=D0=B5=D0=BA=D1=81=D1=82=D0=B0=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=8A=D1=8F=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/components/Reader/Reader.vue | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/components/Reader/Reader.vue b/client/components/Reader/Reader.vue index 54dce485..ff14f991 100644 --- a/client/components/Reader/Reader.vue +++ b/client/components/Reader/Reader.vue @@ -97,8 +97,8 @@
Стартовала ежегодная акция "Оплатим хостинг вместе".

- Для оплаты годового хостинга читалки, необходимо собрать сумму около 2000 рублей. - У автора эти деньги есть. Однако будет справедливо, если каждый + Для оплаты годового хостинга читалки, необходимо собрать около 2000 рублей. + В настоящий момент у автора эта сумма есть в наличии. Однако будет справедливо, если каждый сможет проголосовать рублем за то, чтобы читалка так и оставалась:
    @@ -117,15 +117,17 @@ на читалку через тематические форумы, соцсети, мессенджеры и пр. - Чем нас больше, тем легче оставаться на плаву и тем больше мотивации у автора, чтобы продолжать работать над проектом. + Чем нас больше, тем легче оставаться на плаву и тем больше мотивации у разработчика, чтобы продолжать работать над проектом.

    - Если соберется бóльшая сумма, то разработка децентрализованной библиотеки для свободного обмена книгами будет по воможности ускорена. + Если соберется бóльшая сумма, то разработка децентрализованной библиотеки для свободного обмена книгами будет по возможности ускорена.

    P.S. При необходимости можно воспользоваться подходящим обменником на bestchange.ru

    - Помочь проекту + + Помочь проекту +
From 0b53ad4b4d861e9dd6541c28d6cd03a06c740691 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Tue, 28 Jan 2020 20:20:10 +0700 Subject: [PATCH 16/16] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F=200.?= =?UTF-8?q?8.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/components/Reader/versionHistory.js | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) 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/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",