Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be0f6e57d7 | ||
|
|
b268e9ee74 | ||
|
|
e97774435b | ||
|
|
93586bc5bb | ||
|
|
fe23089714 | ||
|
|
e743986f38 | ||
|
|
a6c9b700ed | ||
|
|
afa3fcb524 | ||
|
|
b9aeb648d6 | ||
|
|
5f5df1e5b7 | ||
|
|
ad885679e4 | ||
|
|
e002bebfbe | ||
|
|
a8a41e2b3d | ||
|
|
31940caa84 | ||
|
|
880334054e | ||
|
|
5f03ad5597 | ||
|
|
1efa3f055d | ||
|
|
8ccf11278b | ||
|
|
8a9e7ab4c3 | ||
|
|
c0fa7c0c51 | ||
|
|
022dfd4709 | ||
|
|
71e08aacc3 | ||
|
|
337eca87f2 | ||
|
|
074aceff8f | ||
|
|
cdc6cf229a | ||
|
|
1f33513dc9 | ||
|
|
b095b91ff2 | ||
|
|
454a62dbb9 | ||
|
|
5f7cc12157 | ||
|
|
97ef1ee201 | ||
|
|
a318568b72 | ||
|
|
5bb9949440 | ||
|
|
c33e91d5d0 | ||
|
|
ca65ef3cb7 | ||
|
|
9ebdbc81d0 | ||
|
|
b64985349e | ||
|
|
625fd9d1a4 | ||
|
|
eac5fdcec0 | ||
|
|
970b4d5d97 | ||
|
|
f741bc818d | ||
|
|
5f04c24187 | ||
|
|
a382bef336 | ||
|
|
4ddf28f344 | ||
|
|
0dc650305a | ||
|
|
697093d1c9 | ||
|
|
622f7a4479 | ||
|
|
c4b607804b | ||
|
|
864f008679 | ||
|
|
25f309bcb0 | ||
|
|
1354361ad9 | ||
|
|
8136c7b072 | ||
|
|
c9243e7249 | ||
|
|
1a487da3d9 | ||
|
|
b52395751c | ||
|
|
cfa6cc9a83 |
@@ -11,7 +11,7 @@
|
||||
<el-tab-pane label="Клавиатура">
|
||||
<HotkeysHelpPage></HotkeysHelpPage>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Мышь/тачпад">
|
||||
<el-tab-pane label="Мышь/тачскрин">
|
||||
<MouseHelpPage></MouseHelpPage>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="История версий" name="releases">
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<h4>Управление с помощью мыши/тачпада:</h4>
|
||||
<h4>Управление с помощью мыши/тачскрина:</h4>
|
||||
<ul>
|
||||
<li><b>ЛКМ/ТАЧ</b> по экрану в одну из областей - активация действия:</li>
|
||||
<div class="click-map-page">
|
||||
<ClickMapPage ref="clickMapPage"></ClickMapPage>
|
||||
</div>
|
||||
|
||||
<li><b>ПКМ</b> - показать/скрыть панель управления</li>
|
||||
<li><b>СКМ</b> - вкл./выкл. плавный скроллинг текста</li>
|
||||
<br>
|
||||
<li>Жесты для тачскрина:</li>
|
||||
<ul>
|
||||
<li style="list-style-type: square">от центра вверх: на весь экран</li>
|
||||
<li style="list-style-type: square">от центра вниз: плавный скроллинг</li>
|
||||
<li style="list-style-type: square">от центра вправо: увеличить скорость скроллинга</li>
|
||||
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
|
||||
</ul>
|
||||
|
||||
</ul>
|
||||
* Для управления с помощью мыши/тачпада необходимо установить галочку "Включить управление кликом" в настройках
|
||||
* Для управления с помощью мыши/тачскрина необходимо установить галочку "Включить управление кликом" в настройках
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div ref="main" class="main">
|
||||
<div class="part">
|
||||
<div class="part top">
|
||||
<span class="greeting bold-font">{{ title }}</span>
|
||||
<div class="space"></div>
|
||||
<span class="greeting">Добро пожаловать!</span>
|
||||
@@ -14,6 +14,7 @@
|
||||
</el-input>
|
||||
<div class="space"></div>
|
||||
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
|
||||
|
||||
<el-button size="mini" @click="loadFileClick">
|
||||
Загрузить файл с диска
|
||||
</el-button>
|
||||
@@ -21,13 +22,16 @@
|
||||
<el-button size="mini" @click="loadBufferClick">
|
||||
Из буфера обмена
|
||||
</el-button>
|
||||
|
||||
<div class="space"></div>
|
||||
<div class="space"></div>
|
||||
<div v-if="mode == 'omnireader'" ref="yaShare2" class="ya-share2"
|
||||
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
||||
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
||||
data-title="Omni Reader - браузерная онлайн-читалка"
|
||||
data-url="https://omnireader.ru">
|
||||
<div v-if="mode == 'omnireader'">
|
||||
<div ref="yaShare2" class="ya-share2"
|
||||
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
||||
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
||||
data-title="Omni Reader - браузерная онлайн-читалка"
|
||||
data-url="https://omnireader.ru">
|
||||
</div>
|
||||
</div>
|
||||
<div class="space"></div>
|
||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
||||
@@ -184,7 +188,7 @@ class LoaderPage extends Vue {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 400px;
|
||||
min-height: 480px;
|
||||
}
|
||||
|
||||
.part {
|
||||
@@ -210,9 +214,14 @@ class LoaderPage extends Vue {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.top {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.center {
|
||||
justify-content: flex-start;
|
||||
padding: 0 10px 0 10px;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
|
||||
@@ -201,6 +201,7 @@ class SearchPage extends Vue {
|
||||
|
||||
.el-button {
|
||||
padding: 9px 17px 9px 17px;
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
i {
|
||||
|
||||
@@ -403,7 +403,7 @@ class ServerStorage extends Vue {
|
||||
|
||||
const md = newRecentMod.data;
|
||||
if (md.key && result[md.key])
|
||||
result[md.key] = utils.applyObjDiff(result[md.key], md.mod);
|
||||
result[md.key] = utils.applyObjDiff(result[md.key], md.mod, true);
|
||||
|
||||
if (!bookManager.loaded) {
|
||||
this.warning('Ожидание загрузки списка книг перед синхронизацией');
|
||||
@@ -463,11 +463,11 @@ class ServerStorage extends Vue {
|
||||
if (itemKey && !needSaveRecentMod) {
|
||||
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
|
||||
newRecentPatch.rev++;
|
||||
newRecentPatch.data[itemKey] = bm.recent[itemKey];
|
||||
newRecentPatch.data[itemKey] = _.cloneDeep(bm.recent[itemKey]);
|
||||
|
||||
let applyMod = this.cachedRecentMod.data;
|
||||
if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
|
||||
newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod);
|
||||
newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod, true);
|
||||
|
||||
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
||||
needSaveRecentPatch = true;
|
||||
@@ -481,7 +481,7 @@ class ServerStorage extends Vue {
|
||||
while (!bookManager.loaded)
|
||||
await utils.sleep(100);
|
||||
|
||||
newRecent = {rev: this.cachedRecent.rev + 1, data: bm.recent};
|
||||
newRecent = {rev: this.cachedRecent.rev + 1, data: _.cloneDeep(bm.recent)};
|
||||
newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}};
|
||||
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
||||
needSaveRecent = true;
|
||||
@@ -510,9 +510,10 @@ class ServerStorage extends Vue {
|
||||
|
||||
if (result.state == 'reject') {
|
||||
|
||||
await this.loadRecent(false, false);
|
||||
const res = await this.loadRecent(false, false);
|
||||
|
||||
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||
if (res)
|
||||
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||
if (!recurse && itemKey) {
|
||||
this.savingRecent = false;
|
||||
this.saveRecent(itemKey, true);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<div v-show="clickControl" ref="layoutEvents" class="layout events" @mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
|
||||
@wheel.prevent.stop="onMouseWheel"
|
||||
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchcancel.prevent.stop="onTouchCancel"
|
||||
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"
|
||||
oncontextmenu="return false;">
|
||||
<div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
||||
@click.prevent.stop="onStatusBarClick"></div>
|
||||
@@ -477,7 +477,7 @@ class TextPage extends Vue {
|
||||
|
||||
generateWaitingFunc(waitingHandlerName, stopPropertyName) {
|
||||
const func = (timeout) => {
|
||||
return new Promise(async(resolve) => {
|
||||
return new Promise((resolve, reject) => { (async() => {
|
||||
this[waitingHandlerName] = resolve;
|
||||
let wait = (timeout + 201)/100;
|
||||
while (wait > 0 && !this[stopPropertyName]) {
|
||||
@@ -485,7 +485,7 @@ class TextPage extends Vue {
|
||||
await sleep(100);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
})().catch(reject); });
|
||||
};
|
||||
return func;
|
||||
}
|
||||
@@ -877,6 +877,14 @@ class TextPage extends Vue {
|
||||
this.$emit('tool-bar-toggle');
|
||||
}
|
||||
|
||||
doScrollingToggle() {
|
||||
this.$emit('scrolling-toggle');
|
||||
}
|
||||
|
||||
doFullScreenToggle() {
|
||||
this.$emit('full-screen-toogle');
|
||||
}
|
||||
|
||||
async doFontSizeInc() {
|
||||
if (!this.settingsChanging) {
|
||||
this.settingsChanging = true;
|
||||
@@ -968,7 +976,7 @@ class TextPage extends Vue {
|
||||
case 'Enter':
|
||||
case 'Backquote'://`
|
||||
case 'KeyF':
|
||||
this.$emit('full-screen-toogle');
|
||||
this.doFullScreenToggle();
|
||||
break;
|
||||
case 'Tab':
|
||||
case 'KeyQ':
|
||||
@@ -1009,22 +1017,64 @@ class TextPage extends Vue {
|
||||
if (!this.$isMobileDevice)
|
||||
return;
|
||||
this.endClickRepeat();
|
||||
|
||||
if (event.touches.length == 1) {
|
||||
const touch = event.touches[0];
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const x = touch.pageX - rect.left;
|
||||
const y = touch.pageY - rect.top;
|
||||
if (this.handleClick(x, y)) {
|
||||
this.repDoing = true;
|
||||
this.debouncedStartClickRepeat(x, y);
|
||||
const hc = this.handleClick(x, y, new Set(['Menu']));
|
||||
if (hc) {
|
||||
if (hc != 'Menu') {
|
||||
this.repDoing = true;
|
||||
this.debouncedStartClickRepeat(x, y);
|
||||
} else {
|
||||
this.startTouch = {x, y};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTouchEnd() {
|
||||
onTouchMove(event) {
|
||||
if (this.startTouch) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
onTouchEnd(event) {
|
||||
if (!this.$isMobileDevice)
|
||||
return;
|
||||
this.endClickRepeat();
|
||||
|
||||
if (event.changedTouches.length == 1) {
|
||||
const touch = event.changedTouches[0];
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const x = touch.pageX - rect.left;
|
||||
const y = touch.pageY - rect.top;
|
||||
if (this.startTouch) {
|
||||
const dy = this.startTouch.y - y;
|
||||
const dx = this.startTouch.x - x;
|
||||
const moveDelta = 30;
|
||||
const touchDelta = 15;
|
||||
if (dy > 0 && Math.abs(dy) >= moveDelta && Math.abs(dy) > Math.abs(dx)) {
|
||||
//движение вверх
|
||||
this.doFullScreenToggle();
|
||||
} else if (dy < 0 && Math.abs(dy) >= moveDelta && Math.abs(dy) > Math.abs(dx)) {
|
||||
//движение вниз
|
||||
this.doScrollingToggle();
|
||||
} else if (dx > 0 && Math.abs(dx) >= moveDelta && Math.abs(dy) < Math.abs(dx)) {
|
||||
//движение влево
|
||||
this.doScrollingSpeedDown();
|
||||
} else if (dx < 0 && Math.abs(dx) >= moveDelta && Math.abs(dy) < Math.abs(dx)) {
|
||||
//движение вправо
|
||||
this.doScrollingSpeedUp();
|
||||
} else if (Math.abs(dy) < touchDelta && Math.abs(dx) < touchDelta) {
|
||||
this.doToolBarToggle();
|
||||
}
|
||||
|
||||
this.startTouch = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTouchCancel() {
|
||||
@@ -1038,12 +1088,13 @@ class TextPage extends Vue {
|
||||
return;
|
||||
this.endClickRepeat();
|
||||
if (event.button == 0) {
|
||||
if (this.handleClick(event.offsetX, event.offsetY)) {
|
||||
const hc = this.handleClick(event.offsetX, event.offsetY);
|
||||
if (hc && hc != 'Menu') {
|
||||
this.repDoing = true;
|
||||
this.debouncedStartClickRepeat(event.offsetX, event.offsetY);
|
||||
}
|
||||
} else if (event.button == 1) {
|
||||
this.$emit('scrolling-toggle');
|
||||
this.doScrollingToggle();
|
||||
} else if (event.button == 2) {
|
||||
this.doToolBarToggle();
|
||||
}
|
||||
@@ -1074,7 +1125,7 @@ class TextPage extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(pointX, pointY) {
|
||||
getClickAction(pointX, pointY) {
|
||||
const w = pointX/this.realWidth*100;
|
||||
const h = pointY/this.realHeight*100;
|
||||
|
||||
@@ -1090,27 +1141,35 @@ class TextPage extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'Down' ://Down
|
||||
this.doDown();
|
||||
break;
|
||||
case 'Up' ://Up
|
||||
this.doUp();
|
||||
break;
|
||||
case 'PgDown' ://PgDown
|
||||
this.doPageDown();
|
||||
break;
|
||||
case 'PgUp' ://PgUp
|
||||
this.doPageUp();
|
||||
break;
|
||||
case 'Menu' :
|
||||
this.doToolBarToggle();
|
||||
break;
|
||||
default :
|
||||
// Nothing
|
||||
return action;
|
||||
}
|
||||
|
||||
handleClick(pointX, pointY, exclude) {
|
||||
const action = this.getClickAction(pointX, pointY);
|
||||
|
||||
if (!exclude || !exclude.has(action)) {
|
||||
switch (action) {
|
||||
case 'Down' ://Down
|
||||
this.doDown();
|
||||
break;
|
||||
case 'Up' ://Up
|
||||
this.doUp();
|
||||
break;
|
||||
case 'PgDown' ://PgDown
|
||||
this.doPageDown();
|
||||
break;
|
||||
case 'PgUp' ://PgUp
|
||||
this.doPageUp();
|
||||
break;
|
||||
case 'Menu' :
|
||||
this.doToolBarToggle();
|
||||
break;
|
||||
default :
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
|
||||
return (action && action != 'Menu');
|
||||
return action;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import he from 'he';
|
||||
import sax from '../../../../server/core/BookConverter/sax';
|
||||
import sax from '../../../../server/core/sax';
|
||||
import {sleep} from '../../../share/utils';
|
||||
|
||||
const maxImageLineCount = 100;
|
||||
@@ -67,7 +67,7 @@ export default class BookParser {
|
||||
}
|
||||
*/
|
||||
const getImageDimensions = (binaryId, binaryType, data) => {
|
||||
return new Promise (async(resolve, reject) => {
|
||||
return new Promise ((resolve, reject) => { (async() => {
|
||||
const i = new Image();
|
||||
let resolved = false;
|
||||
i.onload = () => {
|
||||
@@ -81,19 +81,17 @@ export default class BookParser {
|
||||
resolve();
|
||||
};
|
||||
|
||||
i.onerror = (e) => {
|
||||
reject(e);
|
||||
};
|
||||
i.onerror = reject;
|
||||
|
||||
i.src = `data:${binaryType};base64,${data}`;
|
||||
await sleep(30*1000);
|
||||
if (!resolved)
|
||||
reject('Не удалось получить размер изображения');
|
||||
});
|
||||
})().catch(reject); });
|
||||
};
|
||||
|
||||
const getExternalImageDimensions = (src) => {
|
||||
return new Promise (async(resolve, reject) => {
|
||||
return new Promise ((resolve, reject) => { (async() => {
|
||||
const i = new Image();
|
||||
let resolved = false;
|
||||
i.onload = () => {
|
||||
@@ -105,15 +103,13 @@ export default class BookParser {
|
||||
resolve();
|
||||
};
|
||||
|
||||
i.onerror = (e) => {
|
||||
reject(e);
|
||||
};
|
||||
i.onerror = reject;
|
||||
|
||||
i.src = src;
|
||||
await sleep(30*1000);
|
||||
if (!resolved)
|
||||
reject('Не удалось получить размер изображения');
|
||||
});
|
||||
})().catch(reject); });
|
||||
};
|
||||
|
||||
const newParagraph = (text, len, addIndex) => {
|
||||
|
||||
@@ -1,4 +1,32 @@
|
||||
export const versionHistory = [
|
||||
{
|
||||
showUntil: '2019-11-10',
|
||||
header: '0.7.7 (2019-11-06)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>добавлены следующие жесты для тачскрина (только при включенной опции "управление кликом"):</li>
|
||||
<ul>
|
||||
<li style="list-style-type: square">от центра вверх: на весь экран</li>
|
||||
<li style="list-style-type: square">от центра вниз: плавный скроллинг</li>
|
||||
<li style="list-style-type: square">от центра вправо: увеличить скорость скроллинга</li>
|
||||
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
|
||||
</ul>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-10-29',
|
||||
header: '0.7.6 (2019-10-30)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>исправления багов</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-10-21',
|
||||
header: '0.7.5 (2019-10-22)',
|
||||
|
||||
250
package-lock.json
generated
250
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.7.3",
|
||||
"version": "0.7.5",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -541,11 +541,6 @@
|
||||
"integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==",
|
||||
"dev": true
|
||||
},
|
||||
"adm-zip": {
|
||||
"version": "0.4.13",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz",
|
||||
"integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw=="
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
|
||||
@@ -640,6 +635,57 @@
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
|
||||
},
|
||||
"archiver-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
|
||||
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
|
||||
"requires": {
|
||||
"glob": "^7.1.4",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"lazystream": "^1.0.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.union": "^4.6.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"readable-stream": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
||||
@@ -1868,6 +1914,11 @@
|
||||
"ieee754": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
@@ -2359,6 +2410,51 @@
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"compress-commons": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz",
|
||||
"integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==",
|
||||
"requires": {
|
||||
"buffer-crc32": "^0.2.13",
|
||||
"crc32-stream": "^3.0.1",
|
||||
"normalize-path": "^3.0.0",
|
||||
"readable-stream": "^2.3.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"compressible": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz",
|
||||
@@ -2598,6 +2694,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"crc": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
|
||||
"integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
|
||||
"requires": {
|
||||
"buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"crc32-stream": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz",
|
||||
"integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==",
|
||||
"requires": {
|
||||
"crc": "^3.4.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
|
||||
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"create-ecdh": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
|
||||
@@ -6252,6 +6385,48 @@
|
||||
"webpack-sources": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"lazystream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
|
||||
"integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=",
|
||||
"requires": {
|
||||
"readable-stream": "^2.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lcid": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
|
||||
@@ -6330,12 +6505,37 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
||||
},
|
||||
"lodash.difference": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
|
||||
"integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw="
|
||||
},
|
||||
"lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
|
||||
},
|
||||
"lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.union": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
|
||||
"integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg="
|
||||
},
|
||||
"lodash.uniq": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||
@@ -7011,11 +7211,15 @@
|
||||
"semver": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"node-stream-zip": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.8.2.tgz",
|
||||
"integrity": "sha512-zwP2F/R28Oqtl0gOLItk5QjJ6jEU8XO4kaUMgeqvCyXPgdCZlm8T/5qLMiNy+moJCBCiMQAaX7aVMRhT0t2vkQ=="
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
||||
},
|
||||
"normalize-url": {
|
||||
"version": "4.4.0",
|
||||
@@ -12647,6 +12851,36 @@
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"zip-stream": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.2.tgz",
|
||||
"integrity": "sha512-ykebHGa2+uzth/R4HZLkZh3XFJzivhVsjJt8bN3GvBzLaqqrUdRacu+c4QtnUgjkkQfsOuNE1JgLKMCPNmkKgg==",
|
||||
"requires": {
|
||||
"archiver-utils": "^2.1.0",
|
||||
"compress-commons": "^2.1.1",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
|
||||
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.7.5",
|
||||
"version": "0.7.7",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@@ -55,7 +55,6 @@
|
||||
"webpack-merge": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.4.13",
|
||||
"appcache-webpack-plugin": "^1.4.0",
|
||||
"axios": "^0.18.1",
|
||||
"base-x": "^3.0.6",
|
||||
@@ -72,6 +71,7 @@
|
||||
"lodash": "^4.17.15",
|
||||
"minimist": "^1.2.0",
|
||||
"multer": "^1.4.2",
|
||||
"node-stream-zip": "^1.8.2",
|
||||
"pako": "^1.0.10",
|
||||
"path-browserify": "^1.0.0",
|
||||
"safe-buffer": "^5.2.0",
|
||||
@@ -83,6 +83,7 @@
|
||||
"vue": "github:paulkamer/vue#fix_palemoon_clickhandlers_dist",
|
||||
"vue-router": "^3.1.3",
|
||||
"vuex": "^3.1.1",
|
||||
"vuex-persistedstate": "^2.5.4"
|
||||
"vuex-persistedstate": "^2.5.4",
|
||||
"zip-stream": "^2.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ module.exports = {
|
||||
logDir: `${dataDir}/log`,
|
||||
publicDir: `${execDir}/public`,
|
||||
uploadDir: `${execDir}/public/upload`,
|
||||
sharedDir: `${execDir}/public/shared`,
|
||||
loggingEnabled: true,
|
||||
|
||||
maxUploadFileSize: 50*1024*1024,//50Мб
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
const fs = require('fs-extra');
|
||||
const _ = require('lodash');
|
||||
|
||||
const propsToSave = [
|
||||
'maxUploadFileSize',
|
||||
'maxTempPublicDirSize',
|
||||
'maxUploadPublicDirSize',
|
||||
'useExternalBookConverter',
|
||||
|
||||
'servers',
|
||||
];
|
||||
|
||||
async function load(config, configFilename) {
|
||||
if (!configFilename) {
|
||||
configFilename = `${config.dataDir}/config.json`;
|
||||
|
||||
if (!await fs.pathExists(configFilename)) {
|
||||
save(config);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = await fs.readFile(configFilename, 'utf8');
|
||||
Object.assign(config, JSON.parse(data));
|
||||
}
|
||||
|
||||
async function save(config) {
|
||||
const configFilename = `${config.dataDir}/config.json`;
|
||||
const dataToSave = _.pick(config, propsToSave);
|
||||
|
||||
await fs.writeFile(configFilename, JSON.stringify(dataToSave, null, 4));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
load,
|
||||
save
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
const base = require('./base');
|
||||
|
||||
module.exports = Object.assign({}, base, {
|
||||
branch: 'development',
|
||||
}
|
||||
);
|
||||
branch: 'development',
|
||||
});
|
||||
|
||||
@@ -1,23 +1,91 @@
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs-extra');
|
||||
const utils = require('../core/utils');
|
||||
|
||||
const branchFilename = __dirname + '/application_env';
|
||||
|
||||
let branch = 'production';
|
||||
try {
|
||||
fs.accessSync(branchFilename);
|
||||
branch = fs.readFileSync(branchFilename, 'utf8').trim();
|
||||
} catch (err) {
|
||||
const propsToSave = [
|
||||
'maxUploadFileSize',
|
||||
'maxTempPublicDirSize',
|
||||
'maxUploadPublicDirSize',
|
||||
'useExternalBookConverter',
|
||||
|
||||
'servers',
|
||||
];
|
||||
|
||||
let instance = null;
|
||||
|
||||
//singleton
|
||||
class ConfigManager {
|
||||
constructor() {
|
||||
if (!instance) {
|
||||
this.inited = false;
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.inited)
|
||||
throw new Error('already inited');
|
||||
|
||||
this.branch = 'production';
|
||||
try {
|
||||
await fs.access(branchFilename);
|
||||
this.branch = (await fs.readFile(branchFilename, 'utf8')).trim();
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
|
||||
process.env.NODE_ENV = this.branch;
|
||||
|
||||
this.branchConfigFile = __dirname + `/${this.branch}.js`;
|
||||
await fs.access(this.branchConfigFile);
|
||||
this._config = require(this.branchConfigFile);
|
||||
|
||||
this._userConfigFile = `${this._config.dataDir}/config.json`;
|
||||
|
||||
this.inited = true;
|
||||
}
|
||||
|
||||
get config() {
|
||||
if (!this.inited)
|
||||
throw new Error('not inited');
|
||||
return _.cloneDeep(this._config);
|
||||
}
|
||||
|
||||
set config(value) {
|
||||
Object.assign(this._config, value);
|
||||
}
|
||||
|
||||
get userConfigFile() {
|
||||
return this._userConfigFile;
|
||||
}
|
||||
|
||||
set userConfigFile(value) {
|
||||
if (value)
|
||||
this._userConfigFile = value;
|
||||
}
|
||||
|
||||
async load() {
|
||||
if (!this.inited)
|
||||
throw new Error('not inited');
|
||||
if (!await fs.pathExists(this.userConfigFile)) {
|
||||
await this.save();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await fs.readFile(this.userConfigFile, 'utf8');
|
||||
this.config = JSON.parse(data);
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (!this.inited)
|
||||
throw new Error('not inited');
|
||||
const dataToSave = _.pick(this._config, propsToSave);
|
||||
await fs.writeFile(this.userConfigFile, JSON.stringify(dataToSave, null, 4));
|
||||
}
|
||||
}
|
||||
|
||||
process.env.NODE_ENV = branch;
|
||||
|
||||
const confFilename = __dirname + `/${branch}.js`;
|
||||
|
||||
fs.accessSync(confFilename);
|
||||
|
||||
const config = require(confFilename);
|
||||
|
||||
//fs.ensureDirSync(config.dataDir);
|
||||
|
||||
module.exports = config;
|
||||
module.exports = ConfigManager;
|
||||
@@ -5,21 +5,21 @@ const execDir = path.dirname(process.execPath);
|
||||
const dataDir = `${execDir}/data`;
|
||||
|
||||
module.exports = Object.assign({}, base, {
|
||||
branch: 'production',
|
||||
dataDir: dataDir,
|
||||
tempDir: `${dataDir}/tmp`,
|
||||
logDir: `${dataDir}/log`,
|
||||
publicDir: `${execDir}/public`,
|
||||
uploadDir: `${execDir}/public/upload`,
|
||||
branch: 'production',
|
||||
dataDir: dataDir,
|
||||
tempDir: `${dataDir}/tmp`,
|
||||
logDir: `${dataDir}/log`,
|
||||
publicDir: `${execDir}/public`,
|
||||
uploadDir: `${execDir}/public/upload`,
|
||||
sharedDir: `${execDir}/public/shared`,
|
||||
|
||||
servers: [
|
||||
{
|
||||
serverName: '1',
|
||||
mode: 'normal', //'none', 'normal', 'site', 'reader', 'omnireader'
|
||||
ip: '0.0.0.0',
|
||||
port: '44080',
|
||||
},
|
||||
],
|
||||
servers: [
|
||||
{
|
||||
serverName: '1',
|
||||
mode: 'normal', //'none', 'normal', 'site', 'reader', 'omnireader'
|
||||
ip: '0.0.0.0',
|
||||
port: '44080',
|
||||
},
|
||||
],
|
||||
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const BaseController = require('./BaseController');
|
||||
const log = require('../core/getLogger').getLog();
|
||||
const _ = require('lodash');
|
||||
|
||||
class MiscController extends BaseController {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
const BaseController = require('./BaseController');
|
||||
const ReaderWorker = require('../core/ReaderWorker');
|
||||
const readerStorage = require('../core/readerStorage');
|
||||
const workerState = require('../core/workerState');
|
||||
const ReaderWorker = require('../core/Reader/ReaderWorker');//singleton
|
||||
const ReaderStorage = require('../core/Reader/ReaderStorage');//singleton
|
||||
const WorkerState = require('../core/WorkerState');//singleton
|
||||
|
||||
class ReaderController extends BaseController {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
this.readerStorage = new ReaderStorage();
|
||||
this.readerWorker = new ReaderWorker(config);
|
||||
this.workerState = new WorkerState();
|
||||
}
|
||||
|
||||
async loadBook(req, res) {
|
||||
@@ -19,7 +21,7 @@ class ReaderController extends BaseController {
|
||||
url: request.url,
|
||||
enableSitesFilter: (request.hasOwnProperty('enableSitesFilter') ? request.enableSitesFilter : true)
|
||||
});
|
||||
const state = workerState.getState(workerId);
|
||||
const state = this.workerState.getState(workerId);
|
||||
return (state ? state : {});
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
@@ -38,7 +40,7 @@ class ReaderController extends BaseController {
|
||||
if (!request.items || Array.isArray(request.data))
|
||||
throw new Error(`key 'items' is empty`);
|
||||
|
||||
return await readerStorage.doAction(request);
|
||||
return await this.readerStorage.doAction(request);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
const BaseController = require('./BaseController');
|
||||
const workerState = require('../core/workerState');
|
||||
const WorkerState = require('../core/WorkerState');//singleton
|
||||
|
||||
class WorkerController extends BaseController {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
this.workerState = new WorkerState();
|
||||
}
|
||||
|
||||
async getState(req, res) {
|
||||
const request = req.body;
|
||||
let error = '';
|
||||
@@ -9,7 +14,7 @@ class WorkerController extends BaseController {
|
||||
if (!request.workerId)
|
||||
throw new Error(`key 'workerId' is wrong`);
|
||||
|
||||
const state = workerState.getState(request.workerId);
|
||||
const state = this.workerState.getState(request.workerId);
|
||||
return (state ? state : {});
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
|
||||
50
server/core/AppLogger.js
Normal file
50
server/core/AppLogger.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const fs = require('fs-extra');
|
||||
const Logger = require('./Logger');
|
||||
|
||||
let instance = null;
|
||||
|
||||
//singleton
|
||||
class AppLogger {
|
||||
constructor() {
|
||||
if (!instance) {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
this.inited = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
async init(config) {
|
||||
if (this.inited)
|
||||
throw new Error('already inited');
|
||||
|
||||
let loggerParams = null;
|
||||
|
||||
if (config.loggingEnabled) {
|
||||
await fs.ensureDir(config.logDir);
|
||||
loggerParams = [
|
||||
{log: 'ConsoleLog'},
|
||||
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.log`},
|
||||
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.err.log`, exclude: [LM_OK, LM_INFO]},
|
||||
];
|
||||
}
|
||||
|
||||
this._logger = new Logger(loggerParams);
|
||||
|
||||
this.inited = true;
|
||||
return this.logger;
|
||||
}
|
||||
|
||||
get logger() {
|
||||
if (!this.inited)
|
||||
throw new Error('not inited');
|
||||
return this._logger;
|
||||
}
|
||||
|
||||
get log() {
|
||||
const l = this.logger;
|
||||
return l.log.bind(l);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AppLogger;
|
||||
@@ -1,10 +1,9 @@
|
||||
const fs = require('fs-extra');
|
||||
const zlib = require('zlib');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const unbzip2Stream = require('unbzip2-stream');
|
||||
const tar = require('tar-fs');
|
||||
const AdmZip = require('adm-zip');
|
||||
const ZipStreamer = require('./ZipStreamer');
|
||||
|
||||
const utils = require('./utils');
|
||||
const FileDetector = require('./FileDetector');
|
||||
@@ -112,18 +111,8 @@ class FileDecompressor {
|
||||
}
|
||||
|
||||
async unZip(filename, outputDir) {
|
||||
return new Promise((resolve) => {
|
||||
const files = [];
|
||||
const zip = new AdmZip(filename);
|
||||
|
||||
zip.getEntries().forEach(function(zipEntry) {
|
||||
files.push({path: zipEntry.entryName, size: zipEntry.header.size});
|
||||
});
|
||||
|
||||
zip.extractAllTo(outputDir, true);
|
||||
|
||||
resolve(files);
|
||||
});
|
||||
const zip = new ZipStreamer();
|
||||
return await zip.unpack(filename, outputDir);
|
||||
}
|
||||
|
||||
unBz2(filename, outputDir) {
|
||||
@@ -163,7 +152,7 @@ class FileDecompressor {
|
||||
}
|
||||
|
||||
decompressByStream(stream, filename, outputDir) {
|
||||
return new Promise(async(resolve, reject) => {
|
||||
return new Promise((resolve, reject) => { (async() => {
|
||||
const file = {path: path.parse(filename).name};
|
||||
let outFilename = `${outputDir}/${file.path}`;
|
||||
if (await fs.pathExists(outFilename)) {
|
||||
@@ -183,20 +172,12 @@ class FileDecompressor {
|
||||
resolve([file]);
|
||||
});
|
||||
|
||||
stream.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
inputStream.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
outputStream.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
stream.on('error', reject);
|
||||
inputStream.on('error', reject);
|
||||
outputStream.on('error', reject);
|
||||
|
||||
inputStream.pipe(stream).pipe(outputStream);
|
||||
});
|
||||
})().catch(reject); });
|
||||
}
|
||||
|
||||
async gzipBuffer(buf) {
|
||||
@@ -208,15 +189,26 @@ class FileDecompressor {
|
||||
});
|
||||
}
|
||||
|
||||
async gzipFileIfNotExists(filename, outDir) {
|
||||
const buf = await fs.readFile(filename);
|
||||
async gzipFile(inputFile, outputFile) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const gzip = zlib.createGzip({level: 1});
|
||||
const input = fs.createReadStream(inputFile);
|
||||
const output = fs.createWriteStream(outputFile);
|
||||
|
||||
const hash = crypto.createHash('sha256').update(buf).digest('hex');
|
||||
input.pipe(gzip).pipe(output).on('finish', (err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async gzipFileIfNotExists(filename, outDir) {
|
||||
const hash = await utils.getFileHash(filename, 'sha256', 'hex');
|
||||
|
||||
const outFilename = `${outDir}/${hash}`;
|
||||
|
||||
if (!await fs.pathExists(outFilename)) {
|
||||
await fs.writeFile(outFilename, await this.gzipBuffer(buf))
|
||||
await this.gzipFile(filename, outFilename);
|
||||
} else {
|
||||
await utils.touchFile(outFilename);
|
||||
}
|
||||
|
||||
209
server/core/LibSharedStorage/MegaStorage.js
Normal file
209
server/core/LibSharedStorage/MegaStorage.js
Normal file
@@ -0,0 +1,209 @@
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const log = new (require('../AppLogger'))().log;//singleton
|
||||
const ZipStreamer = require('../ZipStreamer');
|
||||
|
||||
const utils = require('../utils');
|
||||
|
||||
const zeroStats = {
|
||||
zipFilesCount: 0,
|
||||
descFilesCount: 0,
|
||||
zipFilesSize: 0,
|
||||
descFilesSize: 0,
|
||||
};
|
||||
|
||||
let instance = null;
|
||||
|
||||
//singleton
|
||||
class MegaStorage {
|
||||
constructor() {
|
||||
if (!instance) {
|
||||
this.inited = false;
|
||||
|
||||
this.debouncedSaveStats = _.debounce(() => {
|
||||
this.saveStats().catch((e) => {
|
||||
log(LM_ERR, `MegaStorage::saveStats ${e.message}`);
|
||||
//process.exit(1);
|
||||
});
|
||||
}, 5000, {'maxWait':6000});
|
||||
|
||||
process.on('exit', () => {
|
||||
this.saveStatsSync();
|
||||
});
|
||||
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async init(config) {
|
||||
this.config = config;
|
||||
this.megaStorageDir = config.megaStorageDir;
|
||||
this.statsPath = `${this.megaStorageDir}/stats.json`;
|
||||
this.compressLevel = (config.compressLevel ? config.compressLevel : 4);
|
||||
await fs.ensureDir(this.megaStorageDir);
|
||||
|
||||
this.readingFiles = false;
|
||||
this.stats = _.cloneDeep(zeroStats);
|
||||
|
||||
if (await fs.pathExists(this.statsPath)) {
|
||||
this.stats = Object.assign({},
|
||||
this.stats,
|
||||
JSON.parse(await fs.readFile(this.statsPath, 'utf8'))
|
||||
);
|
||||
}
|
||||
|
||||
this.inited = true;
|
||||
}
|
||||
|
||||
async nameHash(filename) {
|
||||
if (!this.inited)
|
||||
throw new Error('not inited');
|
||||
const hash = utils.toBase36(await utils.getFileHash(filename, 'sha1'));
|
||||
const hashPath = `${hash.substr(0, 2)}/${hash.substr(2, 2)}/${hash}`;
|
||||
const fullHashPath = `${this.megaStorageDir}/${hashPath}`;
|
||||
return {
|
||||
filename,
|
||||
hash,
|
||||
hashPath,
|
||||
fullHashPath,
|
||||
zipPath: `${fullHashPath}.zip`,
|
||||
descPath: `${fullHashPath}.desc`,
|
||||
};
|
||||
}
|
||||
|
||||
async checkFileExists(nameHash) {
|
||||
return await fs.pathExists(nameHash.zipPath);
|
||||
}
|
||||
|
||||
async addFile(nameHash, desc = null, force = false) {
|
||||
if (!this.inited)
|
||||
throw new Error('not inited');
|
||||
if (await this.checkFileExists(nameHash) && !force)
|
||||
return false;
|
||||
|
||||
await fs.ensureDir(path.dirname(nameHash.zipPath));
|
||||
let oldZipSize = 0;
|
||||
let newZipCount = 1;
|
||||
if (await fs.pathExists(nameHash.zipPath)) {
|
||||
oldZipSize = (await fs.stat(nameHash.zipPath)).size;
|
||||
newZipCount = 0;
|
||||
}
|
||||
|
||||
const zip = new ZipStreamer();
|
||||
let entry = {};
|
||||
let resultFile = await zip.pack(nameHash.zipPath, [nameHash.filename], {zlib: {level: this.compressLevel}}, (ent) => {
|
||||
entry = ent;
|
||||
});
|
||||
|
||||
if (desc) {
|
||||
desc = Object.assign({}, desc, {fileSize: entry.size, zipFileSize: resultFile.size});
|
||||
await this.updateDesc(nameHash, desc);
|
||||
}
|
||||
|
||||
this.stats.zipFilesSize += -oldZipSize + resultFile.size;
|
||||
this.stats.zipFilesCount += newZipCount;
|
||||
this.needSaveStats = true;
|
||||
|
||||
this.debouncedSaveStats();
|
||||
return desc;
|
||||
}
|
||||
|
||||
async updateDesc(nameHash, desc) {
|
||||
let oldDescSize = 0;
|
||||
let newDescCount = 1;
|
||||
if (await fs.pathExists(nameHash.descPath)) {
|
||||
oldDescSize = (await fs.stat(nameHash.descPath)).size;
|
||||
newDescCount = 0;
|
||||
}
|
||||
|
||||
const data = JSON.stringify(desc, null, 2);
|
||||
await fs.writeFile(nameHash.descPath, data);
|
||||
|
||||
this.stats.descFilesSize += -oldDescSize + data.length;
|
||||
this.stats.descFilesCount += newDescCount;
|
||||
this.needSaveStats = true;
|
||||
|
||||
this.debouncedSaveStats();
|
||||
}
|
||||
|
||||
async _findFiles(callback, dir) {
|
||||
if (!callback || !this.readingFiles)
|
||||
return;
|
||||
|
||||
let result = true;
|
||||
const files = await fs.readdir(dir, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
if (!this.readingFiles)
|
||||
return;
|
||||
const found = path.resolve(dir, file.name);
|
||||
if (file.isDirectory())
|
||||
result = await this._findFiles(callback, found);
|
||||
else
|
||||
await callback(found);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async startFindFiles(callback) {
|
||||
if (!this.inited)
|
||||
throw new Error('not inited');
|
||||
this.readingFiles = true;
|
||||
try {
|
||||
return await this._findFiles(callback, this.megaStorageDir);
|
||||
} finally {
|
||||
this.readingFiles = false;
|
||||
}
|
||||
}
|
||||
|
||||
async stopFindFiles() {
|
||||
this.readingFiles = false;
|
||||
}
|
||||
|
||||
async saveStats() {
|
||||
if (this.needSaveStats) {
|
||||
await fs.writeFile(this.statsPath, JSON.stringify(this.stats, null, 2));
|
||||
this.needSaveStats = false;
|
||||
}
|
||||
}
|
||||
|
||||
saveStatsSync() {
|
||||
if (this.needSaveStats) {
|
||||
fs.writeFileSync(this.statsPath, JSON.stringify(this.stats, null, 2));
|
||||
this.needSaveStats = false;
|
||||
}
|
||||
}
|
||||
|
||||
async getStats(gather = false) {
|
||||
if (!this.inited)
|
||||
throw new Error('MegaStorage::not inited');
|
||||
if (!gather || this.readingFiles)
|
||||
return this.stats;
|
||||
|
||||
let stats = _.cloneDeep(zeroStats);
|
||||
const result = await this.startFindFiles(async(entry) => {
|
||||
if (path.extname(entry) == '.zip') {
|
||||
stats.zipFilesSize += (await fs.stat(entry)).size;
|
||||
stats.zipFilesCount++;
|
||||
}
|
||||
|
||||
if (path.extname(entry) == '.desc') {
|
||||
stats.descFilesSize += (await fs.stat(entry)).size;
|
||||
stats.descFilesCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (result) {
|
||||
this.stats = stats;
|
||||
this.needSaveStats = true;
|
||||
this.debouncedSaveStats();
|
||||
}
|
||||
return this.stats;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MegaStorage;
|
||||
4
server/core/LibSharedStorage/index.js
Normal file
4
server/core/LibSharedStorage/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
class LibSharedStorage {
|
||||
}
|
||||
|
||||
module.exports = LibSharedStorage;
|
||||
@@ -4,7 +4,7 @@ const chardet = require('chardet');
|
||||
const he = require('he');
|
||||
|
||||
const textUtils = require('./textUtils');
|
||||
const utils = require('../utils');
|
||||
const utils = require('../../utils');
|
||||
|
||||
let execConverterCounter = 0;
|
||||
|
||||
@@ -82,7 +82,7 @@ class ConvertBase {
|
||||
}
|
||||
|
||||
escapeEntities(text) {
|
||||
return he.escape(he.decode(text));
|
||||
return he.escape(he.decode(text.replace(/ /g, ' ')));
|
||||
}
|
||||
|
||||
formatFb2(fb2) {
|
||||
@@ -1,5 +1,5 @@
|
||||
const ConvertBase = require('./ConvertBase');
|
||||
const sax = require('./sax');
|
||||
const sax = require('../../sax');
|
||||
const textUtils = require('./textUtils');
|
||||
|
||||
class ConvertHtml extends ConvertBase {
|
||||
@@ -64,6 +64,7 @@ class ConvertHtml extends ConvertBase {
|
||||
for (let line of lines) {
|
||||
if (line.trim() == '')
|
||||
continue;
|
||||
|
||||
line = repCrLfTab(line);
|
||||
|
||||
let l = 0;
|
||||
@@ -171,7 +172,6 @@ class ConvertHtml extends ConvertBase {
|
||||
});
|
||||
|
||||
titleInfo['book-title'] = title;
|
||||
|
||||
//подозрение на чистый текст, надо разбить на параграфы
|
||||
if (isText || pars.length < buf.length/2000) {
|
||||
let total = 0;
|
||||
@@ -257,8 +257,9 @@ class ConvertHtml extends ConvertBase {
|
||||
|
||||
pars[i]._t = this.repSpaces(pars[i]._t).trim();
|
||||
|
||||
if (pars[i]._t.indexOf('<') >= 0) {
|
||||
if (pars[i]._t.indexOf('<') >= 0 || bold || italic) {
|
||||
const t = pars[i]._t;
|
||||
|
||||
let a = [];
|
||||
|
||||
const onTextNode = (text) => {
|
||||
@@ -1,8 +1,8 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const sax = require('./sax');
|
||||
const utils = require('../utils');
|
||||
const sax = require('../../sax');
|
||||
const utils = require('../../utils');
|
||||
const ConvertHtml = require('./ConvertHtml');
|
||||
|
||||
class ConvertPdf extends ConvertHtml {
|
||||
@@ -1,7 +1,7 @@
|
||||
const _ = require('lodash');
|
||||
const URL = require('url').URL;
|
||||
|
||||
const sax = require('./sax');
|
||||
const sax = require('../../sax');
|
||||
const ConvertBase = require('./ConvertBase');
|
||||
|
||||
class ConvertSamlib extends ConvertBase {
|
||||
@@ -1,5 +1,5 @@
|
||||
const fs = require('fs-extra');
|
||||
const FileDetector = require('../FileDetector');
|
||||
const FileDetector = require('../../FileDetector');
|
||||
|
||||
//порядок важен
|
||||
const convertClassFactory = [
|
||||
@@ -1,12 +1,22 @@
|
||||
const SQL = require('sql-template-strings');
|
||||
const _ = require('lodash');
|
||||
|
||||
const connManager = require('../db/connManager');
|
||||
const ConnManager = require('../../db/ConnManager');//singleton
|
||||
|
||||
let instance = null;
|
||||
|
||||
//singleton
|
||||
class ReaderStorage {
|
||||
constructor() {
|
||||
this.storagePool = connManager.pool.readerStorage;
|
||||
this.periodicCleanCache(3*3600*1000);//1 раз в 3 часа
|
||||
if (!instance) {
|
||||
this.connManager = new ConnManager();
|
||||
this.storagePool = this.connManager.pool.readerStorage;
|
||||
this.periodicCleanCache(3*3600*1000);//1 раз в 3 часа
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async doAction(act) {
|
||||
@@ -113,6 +123,4 @@ class ReaderStorage {
|
||||
}
|
||||
}
|
||||
|
||||
const readerStorage = new ReaderStorage();
|
||||
|
||||
module.exports = readerStorage;
|
||||
module.exports = ReaderStorage;
|
||||
@@ -1,35 +1,40 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const workerState = require('./workerState');
|
||||
const FileDownloader = require('./FileDownloader');
|
||||
const FileDecompressor = require('./FileDecompressor');
|
||||
const WorkerState = require('../WorkerState');//singleton
|
||||
const FileDownloader = require('../FileDownloader');
|
||||
const FileDecompressor = require('../FileDecompressor');
|
||||
const BookConverter = require('./BookConverter');
|
||||
const utils = require('./utils');
|
||||
const log = require('./getLogger').getLog();
|
||||
|
||||
let singleCleanExecute = false;
|
||||
const utils = require('../utils');
|
||||
const log = new (require('../AppLogger'))().log;//singleton
|
||||
|
||||
let instance = null;
|
||||
|
||||
//singleton
|
||||
class ReaderWorker {
|
||||
constructor(config) {
|
||||
this.config = Object.assign({}, config);
|
||||
|
||||
this.config.tempDownloadDir = `${config.tempDir}/download`;
|
||||
fs.ensureDirSync(this.config.tempDownloadDir);
|
||||
if (!instance) {
|
||||
this.config = Object.assign({}, config);
|
||||
|
||||
this.config.tempDownloadDir = `${config.tempDir}/download`;
|
||||
fs.ensureDirSync(this.config.tempDownloadDir);
|
||||
|
||||
this.config.tempPublicDir = `${config.publicDir}/tmp`;
|
||||
fs.ensureDirSync(this.config.tempPublicDir);
|
||||
this.config.tempPublicDir = `${config.publicDir}/tmp`;
|
||||
fs.ensureDirSync(this.config.tempPublicDir);
|
||||
|
||||
this.down = new FileDownloader();
|
||||
this.decomp = new FileDecompressor();
|
||||
this.bookConverter = new BookConverter(this.config);
|
||||
this.workerState = new WorkerState();
|
||||
this.down = new FileDownloader();
|
||||
this.decomp = new FileDecompressor();
|
||||
this.bookConverter = new BookConverter(this.config);
|
||||
|
||||
if (!singleCleanExecute) {
|
||||
this.periodicCleanDir(this.config.tempPublicDir, this.config.maxTempPublicDirSize, 60*60*1000);//1 раз в час
|
||||
this.periodicCleanDir(this.config.uploadDir, this.config.maxUploadPublicDirSize, 60*60*1000);//1 раз в час
|
||||
singleCleanExecute = true;
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async loadBook(opts, wState) {
|
||||
@@ -69,8 +74,7 @@ class ReaderWorker {
|
||||
try {
|
||||
decompFiles = await this.decomp.decompressNested(downloadedFilename, decompDir);
|
||||
} catch (e) {
|
||||
if (this.config.branch == 'development')
|
||||
console.error(e);
|
||||
log(LM_ERR, e.stack);
|
||||
throw new Error('Ошибка распаковки');
|
||||
}
|
||||
wState.set({progress: 100});
|
||||
@@ -92,8 +96,7 @@ class ReaderWorker {
|
||||
wState.finish({path: `/tmp/${finishFilename}`});
|
||||
|
||||
} catch (e) {
|
||||
if (this.config.branch == 'development')
|
||||
console.error(e);
|
||||
log(LM_ERR, e.stack);
|
||||
wState.set({state: 'error', error: (errMes ? errMes : e.message)});
|
||||
} finally {
|
||||
//clean
|
||||
@@ -107,8 +110,8 @@ class ReaderWorker {
|
||||
}
|
||||
|
||||
loadBookUrl(opts) {
|
||||
const workerId = workerState.generateWorkerId();
|
||||
const wState = workerState.getControl(workerId);
|
||||
const workerId = this.workerState.generateWorkerId();
|
||||
const wState = this.workerState.getControl(workerId);
|
||||
wState.set({state: 'start'});
|
||||
|
||||
this.loadBook(opts, wState);
|
||||
@@ -117,10 +120,7 @@ class ReaderWorker {
|
||||
}
|
||||
|
||||
async saveFile(file) {
|
||||
const buf = await fs.readFile(file.path);
|
||||
|
||||
const hash = crypto.createHash('sha256').update(buf).digest('hex');
|
||||
|
||||
const hash = await utils.getFileHash(file.path, 'sha256', 'hex');
|
||||
const outFilename = `${this.config.uploadDir}/${hash}`;
|
||||
|
||||
if (!await fs.pathExists(outFilename)) {
|
||||
@@ -135,7 +135,6 @@ class ReaderWorker {
|
||||
|
||||
async periodicCleanDir(dir, maxSize, timeout) {
|
||||
try {
|
||||
log(`Start clean dir: ${dir}, maxSize=${maxSize}`);
|
||||
const list = await fs.readdir(dir);
|
||||
|
||||
let size = 0;
|
||||
@@ -147,21 +146,20 @@ class ReaderWorker {
|
||||
files.push({name, stat});
|
||||
}
|
||||
}
|
||||
log(`found ${files.length} files in dir ${dir}`);
|
||||
log(`clean dir ${dir}, maxSize=${maxSize}, found ${files.length} files`);
|
||||
|
||||
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
||||
|
||||
let i = 0;
|
||||
while (i < files.length && size > maxSize) {
|
||||
const file = files[i];
|
||||
log(`rm ${dir}/${file.name}`);
|
||||
await fs.remove(`${dir}/${file.name}`);
|
||||
size -= file.stat.size;
|
||||
i++;
|
||||
}
|
||||
log(`removed ${i} files`);
|
||||
} catch(e) {
|
||||
log(LM_ERR, e.message);
|
||||
log(LM_ERR, e.stack);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.periodicCleanDir(dir, maxSize, timeout);
|
||||
@@ -3,10 +3,18 @@ const utils = require('./utils');
|
||||
const cleanInterval = 3600; //sec
|
||||
const cleanAfterLastModified = cleanInterval - 60; //sec
|
||||
|
||||
let instance = null;
|
||||
|
||||
//singleton
|
||||
class WorkerState {
|
||||
constructor() {
|
||||
this.states = {};
|
||||
setTimeout(this.cleanStates.bind(this), cleanInterval*1000);
|
||||
if (!instance) {
|
||||
this.states = {};
|
||||
this.cleanStates();
|
||||
instance = this;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
generateWorkerId() {
|
||||
@@ -51,6 +59,4 @@ class WorkerState {
|
||||
}
|
||||
}
|
||||
|
||||
const workerState = new WorkerState();
|
||||
|
||||
module.exports = workerState;
|
||||
module.exports = WorkerState;
|
||||
80
server/core/ZipStreamer.js
Normal file
80
server/core/ZipStreamer.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const zipStream = require('zip-stream');
|
||||
const unzipStream = require('node-stream-zip');
|
||||
|
||||
class ZipStreamer {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
//TODO: сделать рекурсивный обход директорий, пока только файлы
|
||||
//files = ['filename', 'dirname/']
|
||||
pack(zipFile, files, options, entryCallback) {
|
||||
return new Promise((resolve, reject) => { (async() => {
|
||||
entryCallback = (entryCallback ? entryCallback : () => {});
|
||||
const zip = new zipStream(options);
|
||||
|
||||
const outputStream = fs.createWriteStream(zipFile);
|
||||
|
||||
outputStream.on('error', reject);
|
||||
outputStream.on('finish', async() => {
|
||||
let file = {path: zipFile};
|
||||
try {
|
||||
file.size = (await fs.stat(zipFile)).size;
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
resolve(file);
|
||||
});
|
||||
|
||||
zip.on('error', reject);
|
||||
zip.pipe(outputStream);
|
||||
|
||||
const zipAddEntry = (filename) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const basename = path.basename(filename);
|
||||
const source = fs.createReadStream(filename);
|
||||
|
||||
zip.entry(source, {name: basename}, (err, entry) => {
|
||||
if (err) reject(err);
|
||||
resolve(entry);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
for (const filename of files) {
|
||||
const entry = await zipAddEntry(filename);
|
||||
entryCallback({path: entry.name, size: entry.size, compressedSize: entry.csize});
|
||||
}
|
||||
|
||||
zip.finish();
|
||||
})().catch(reject); });
|
||||
}
|
||||
|
||||
unpack(zipFile, outputDir, entryCallback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
entryCallback = (entryCallback ? entryCallback : () => {});
|
||||
const unzip = new unzipStream({file: zipFile});
|
||||
|
||||
unzip.on('error', reject);
|
||||
|
||||
let files = [];
|
||||
unzip.on('extract', (en) => {
|
||||
const entry = {path: en.name, size: en.size, compressedSize: en.compressedSize};
|
||||
entryCallback(entry);
|
||||
files.push(entry);
|
||||
});
|
||||
|
||||
unzip.on('ready', () => {
|
||||
unzip.extract(null, outputDir, (err) => {
|
||||
if (err) reject(err);
|
||||
unzip.close();
|
||||
resolve(files);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ZipStreamer;
|
||||
@@ -1,40 +0,0 @@
|
||||
const fs = require('fs-extra');
|
||||
const Logger = require('./Logger');
|
||||
|
||||
let logger = null;
|
||||
|
||||
function initLogger(config) {
|
||||
if (logger)
|
||||
logger.close();
|
||||
|
||||
let loggerParams = null;
|
||||
|
||||
if (config.loggingEnabled) {
|
||||
fs.ensureDirSync(config.logDir);
|
||||
loggerParams = [
|
||||
{log: 'ConsoleLog'},
|
||||
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.log`},
|
||||
];
|
||||
}
|
||||
|
||||
logger = new Logger(loggerParams);
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
function getLogger() {
|
||||
if (logger)
|
||||
return logger;
|
||||
throw new Error('getLogger error: logger not initialized');
|
||||
}
|
||||
|
||||
function getLog() {
|
||||
const l = getLogger();
|
||||
return l.log.bind(l);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initLogger,
|
||||
getLogger,
|
||||
getLog,
|
||||
};
|
||||
@@ -1,6 +1,28 @@
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs-extra');
|
||||
const crypto = require('crypto');
|
||||
const baseX = require('base-x');
|
||||
|
||||
const BASE36 = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
const bs36 = baseX(BASE36);
|
||||
|
||||
function toBase36(data) {
|
||||
return bs36.encode(Buffer.from(data));
|
||||
}
|
||||
|
||||
function fromBase36(data) {
|
||||
return bs36.decode(data);
|
||||
}
|
||||
|
||||
function getFileHash(filename, hashName, enc) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash(hashName);
|
||||
const rs = fs.createReadStream(filename);
|
||||
rs.on('error', reject);
|
||||
rs.on('data', chunk => hash.update(chunk));
|
||||
rs.on('end', () => resolve(hash.digest(enc)));
|
||||
});
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
@@ -20,7 +42,7 @@ function spawnProcess(cmd, opts) {
|
||||
onData = (onData ? onData : () => {});
|
||||
args = (args ? args : []);
|
||||
|
||||
return new Promise(async(resolve, reject) => {
|
||||
return new Promise((resolve, reject) => { (async() => {
|
||||
let resolved = false;
|
||||
const proc = spawn(cmd, args, {detached: true});
|
||||
|
||||
@@ -50,10 +72,13 @@ function spawnProcess(cmd, opts) {
|
||||
process.kill(proc.pid);
|
||||
reject({status: 'killed', stdout, stderr});
|
||||
}
|
||||
});
|
||||
})().catch(reject); });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
toBase36,
|
||||
fromBase36,
|
||||
getFileHash,
|
||||
sleep,
|
||||
randomHexString,
|
||||
touchFile,
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const SqliteConnectionPool = require('./SqliteConnectionPool');
|
||||
const log = require('../core/getLogger').getLog();
|
||||
const log = new (require('../core/AppLogger'))().log;//singleton
|
||||
|
||||
const migrations = {
|
||||
'app': require('./migrations/app'),
|
||||
'readerStorage': require('./migrations/readerStorage'),
|
||||
};
|
||||
|
||||
let instance = null;
|
||||
|
||||
//singleton
|
||||
class ConnManager {
|
||||
constructor() {
|
||||
this._pool = {};
|
||||
if (!instance) {
|
||||
this.inited = false;
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async init(config) {
|
||||
this.config = config;
|
||||
this._pool = {};
|
||||
|
||||
const force = null;//(config.branch == 'development' ? 'last' : null);
|
||||
|
||||
@@ -39,6 +49,7 @@ class ConnManager {
|
||||
|
||||
this._pool[poolConfig.poolName] = connPool;
|
||||
}
|
||||
this.inited = true;
|
||||
}
|
||||
|
||||
get pool() {
|
||||
@@ -46,6 +57,4 @@ class ConnManager {
|
||||
}
|
||||
}
|
||||
|
||||
const connManager = new ConnManager();
|
||||
|
||||
module.exports = connManager;
|
||||
module.exports = ConnManager;
|
||||
@@ -1,4 +1,4 @@
|
||||
const log = require('./core/getLogger').getLog();
|
||||
const log = new (require('./core/AppLogger'))().log;//singleton
|
||||
|
||||
function webpackDevMiddleware(app) {
|
||||
const webpack = require('webpack');
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
const config = require('./config');
|
||||
const logger = require('./core/getLogger');
|
||||
logger.initLogger(config);
|
||||
const log = logger.getLog();
|
||||
|
||||
const configSaver = require('./config/configSaver');
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
const express = require('express');
|
||||
const compression = require('compression');
|
||||
|
||||
const connManager = require('./db/connManager');
|
||||
|
||||
async function init() {
|
||||
//config
|
||||
const configManager = new (require('./config'))();//singleton
|
||||
await configManager.init();
|
||||
configManager.userConfigFile = argv.config;
|
||||
await configManager.load();
|
||||
const config = configManager.config;
|
||||
|
||||
//logger
|
||||
const appLogger = new (require('./core/AppLogger'))();//singleton
|
||||
await appLogger.init(config);
|
||||
const log = appLogger.log;
|
||||
|
||||
//dirs
|
||||
log(`${config.name} v${config.version}`);
|
||||
log('Initializing');
|
||||
|
||||
await fs.ensureDir(config.dataDir);
|
||||
await fs.ensureDir(config.uploadDir);
|
||||
await fs.ensureDir(config.sharedDir);
|
||||
|
||||
await fs.ensureDir(config.tempDir);
|
||||
await fs.emptyDir(config.tempDir);
|
||||
|
||||
@@ -26,16 +35,14 @@ async function init() {
|
||||
await fs.move(appNewDir, appDir);
|
||||
}
|
||||
|
||||
//загружаем конфиг из файла
|
||||
await configSaver.load(config, argv.config);
|
||||
//connections
|
||||
const connManager = new (require('./db/ConnManager'))();//singleton
|
||||
await connManager.init(config);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
log(`${config.name} v${config.version}`);
|
||||
log('Initializing');
|
||||
await init();
|
||||
|
||||
await connManager.init(config);
|
||||
async function main() {
|
||||
const log = new (require('./core/AppLogger'))().log;//singleton
|
||||
const config = new (require('./config'))().config;//singleton
|
||||
|
||||
//servers
|
||||
for (let server of config.servers) {
|
||||
@@ -86,6 +93,7 @@ async function main() {
|
||||
|
||||
(async() => {
|
||||
try {
|
||||
await init();
|
||||
await main();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
Reference in New Issue
Block a user