Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bbfdc2cb2 | ||
|
|
211fec35e3 | ||
|
|
b8214a46ae | ||
|
|
549ef91c81 | ||
|
|
cede65313b | ||
|
|
d897a7400f | ||
|
|
47f059213f | ||
|
|
8af51bbf08 | ||
|
|
53d9f5ddc6 | ||
|
|
06fffdccc8 | ||
|
|
aa13dc68fc | ||
|
|
813876dd90 | ||
|
|
596c7d65c5 | ||
|
|
ce8dcb75bf | ||
|
|
1bd51b5565 | ||
|
|
1f9ec305b4 | ||
|
|
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 |
@@ -1,3 +1,2 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow: /?*url=
|
Disallow: /?*url=
|
||||||
Disallow: /#/
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<el-tab-pane label="Клавиатура">
|
<el-tab-pane label="Клавиатура">
|
||||||
<HotkeysHelpPage></HotkeysHelpPage>
|
<HotkeysHelpPage></HotkeysHelpPage>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="Мышь/тачпад">
|
<el-tab-pane label="Мышь/тачскрин">
|
||||||
<MouseHelpPage></MouseHelpPage>
|
<MouseHelpPage></MouseHelpPage>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="История версий" name="releases">
|
<el-tab-pane label="История версий" name="releases">
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<h4>Управление с помощью мыши/тачпада:</h4>
|
<h4>Управление с помощью мыши/тачскрина:</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>ЛКМ/ТАЧ</b> по экрану в одну из областей - активация действия:</li>
|
<li><b>ЛКМ/ТАЧ</b> по экрану в одну из областей - активация действия:</li>
|
||||||
<div class="click-map-page">
|
<div class="click-map-page">
|
||||||
<ClickMapPage ref="clickMapPage"></ClickMapPage>
|
<ClickMapPage ref="clickMapPage"></ClickMapPage>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<li><b>ПКМ</b> - показать/скрыть панель управления</li>
|
<li><b>ПКМ</b> - показать/скрыть панель управления</li>
|
||||||
<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>
|
||||||
* Для управления с помощью мыши/тачпада необходимо установить галочку "Включить управление кликом" в настройках
|
|
||||||
|
</ul>
|
||||||
|
* Для управления с помощью мыши/тачскрина необходимо установить галочку "Включить управление кликом" в настройках
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="main">
|
<div ref="main" class="main">
|
||||||
<div class="part">
|
<div class="part top">
|
||||||
<span class="greeting bold-font">{{ title }}</span>
|
<span class="greeting bold-font">{{ title }}</span>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<span class="greeting">Добро пожаловать!</span>
|
<span class="greeting">Добро пожаловать!</span>
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
|
<input type="file" id="file" ref="file" @change="loadFile" style='display: none;'/>
|
||||||
|
|
||||||
<el-button size="mini" @click="loadFileClick">
|
<el-button size="mini" @click="loadFileClick">
|
||||||
Загрузить файл с диска
|
Загрузить файл с диска
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -21,14 +22,17 @@
|
|||||||
<el-button size="mini" @click="loadBufferClick">
|
<el-button size="mini" @click="loadBufferClick">
|
||||||
Из буфера обмена
|
Из буфера обмена
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<div v-if="mode == 'omnireader'" ref="yaShare2" class="ya-share2"
|
<div v-if="mode == 'omnireader'">
|
||||||
|
<div ref="yaShare2" class="ya-share2"
|
||||||
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
|
||||||
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
|
||||||
data-title="Omni Reader - браузерная онлайн-читалка"
|
data-title="Omni Reader - браузерная онлайн-читалка"
|
||||||
data-url="https://omnireader.ru">
|
data-url="https://omnireader.ru">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
||||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openOldVersion">Старая версия</span>
|
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openOldVersion">Старая версия</span>
|
||||||
@@ -184,7 +188,7 @@ class LoaderPage extends Vue {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 400px;
|
min-height: 480px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.part {
|
.part {
|
||||||
@@ -210,9 +214,14 @@ class LoaderPage extends Vue {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: 0 10px 0 10px;
|
padding: 0 10px 0 10px;
|
||||||
|
min-height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom {
|
.bottom {
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ class SearchPage extends Vue {
|
|||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
padding: 9px 17px 9px 17px;
|
padding: 9px 17px 9px 17px;
|
||||||
|
width: 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ class ServerStorage extends Vue {
|
|||||||
|
|
||||||
const md = newRecentMod.data;
|
const md = newRecentMod.data;
|
||||||
if (md.key && result[md.key])
|
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) {
|
if (!bookManager.loaded) {
|
||||||
this.warning('Ожидание загрузки списка книг перед синхронизацией');
|
this.warning('Ожидание загрузки списка книг перед синхронизацией');
|
||||||
@@ -463,11 +463,11 @@ class ServerStorage extends Vue {
|
|||||||
if (itemKey && !needSaveRecentMod) {
|
if (itemKey && !needSaveRecentMod) {
|
||||||
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
|
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
|
||||||
newRecentPatch.rev++;
|
newRecentPatch.rev++;
|
||||||
newRecentPatch.data[itemKey] = bm.recent[itemKey];
|
newRecentPatch.data[itemKey] = _.cloneDeep(bm.recent[itemKey]);
|
||||||
|
|
||||||
let applyMod = this.cachedRecentMod.data;
|
let applyMod = this.cachedRecentMod.data;
|
||||||
if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
|
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: {}};
|
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
||||||
needSaveRecentPatch = true;
|
needSaveRecentPatch = true;
|
||||||
@@ -481,7 +481,7 @@ class ServerStorage extends Vue {
|
|||||||
while (!bookManager.loaded)
|
while (!bookManager.loaded)
|
||||||
await utils.sleep(100);
|
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: {}};
|
newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}};
|
||||||
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
||||||
needSaveRecent = true;
|
needSaveRecent = true;
|
||||||
@@ -510,8 +510,9 @@ class ServerStorage extends Vue {
|
|||||||
|
|
||||||
if (result.state == 'reject') {
|
if (result.state == 'reject') {
|
||||||
|
|
||||||
await this.loadRecent(false, false);
|
const res = await this.loadRecent(false, false);
|
||||||
|
|
||||||
|
if (res)
|
||||||
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||||
if (!recurse && itemKey) {
|
if (!recurse && itemKey) {
|
||||||
this.savingRecent = false;
|
this.savingRecent = false;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-show="clickControl" ref="layoutEvents" class="layout events" @mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
|
<div v-show="clickControl" ref="layoutEvents" class="layout events" @mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
|
||||||
@wheel.prevent.stop="onMouseWheel"
|
@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;">
|
oncontextmenu="return false;">
|
||||||
<div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
<div v-show="showStatusBar" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
||||||
@click.prevent.stop="onStatusBarClick"></div>
|
@click.prevent.stop="onStatusBarClick"></div>
|
||||||
@@ -477,7 +477,7 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
generateWaitingFunc(waitingHandlerName, stopPropertyName) {
|
generateWaitingFunc(waitingHandlerName, stopPropertyName) {
|
||||||
const func = (timeout) => {
|
const func = (timeout) => {
|
||||||
return new Promise(async(resolve) => {
|
return new Promise((resolve, reject) => { (async() => {
|
||||||
this[waitingHandlerName] = resolve;
|
this[waitingHandlerName] = resolve;
|
||||||
let wait = (timeout + 201)/100;
|
let wait = (timeout + 201)/100;
|
||||||
while (wait > 0 && !this[stopPropertyName]) {
|
while (wait > 0 && !this[stopPropertyName]) {
|
||||||
@@ -485,7 +485,7 @@ class TextPage extends Vue {
|
|||||||
await sleep(100);
|
await sleep(100);
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
});
|
})().catch(reject); });
|
||||||
};
|
};
|
||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
@@ -877,6 +877,14 @@ class TextPage extends Vue {
|
|||||||
this.$emit('tool-bar-toggle');
|
this.$emit('tool-bar-toggle');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doScrollingToggle() {
|
||||||
|
this.$emit('scrolling-toggle');
|
||||||
|
}
|
||||||
|
|
||||||
|
doFullScreenToggle() {
|
||||||
|
this.$emit('full-screen-toogle');
|
||||||
|
}
|
||||||
|
|
||||||
async doFontSizeInc() {
|
async doFontSizeInc() {
|
||||||
if (!this.settingsChanging) {
|
if (!this.settingsChanging) {
|
||||||
this.settingsChanging = true;
|
this.settingsChanging = true;
|
||||||
@@ -968,7 +976,7 @@ class TextPage extends Vue {
|
|||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'Backquote'://`
|
case 'Backquote'://`
|
||||||
case 'KeyF':
|
case 'KeyF':
|
||||||
this.$emit('full-screen-toogle');
|
this.doFullScreenToggle();
|
||||||
break;
|
break;
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
case 'KeyQ':
|
case 'KeyQ':
|
||||||
@@ -1009,22 +1017,64 @@ class TextPage extends Vue {
|
|||||||
if (!this.$isMobileDevice)
|
if (!this.$isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
|
|
||||||
if (event.touches.length == 1) {
|
if (event.touches.length == 1) {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
const rect = event.target.getBoundingClientRect();
|
const rect = event.target.getBoundingClientRect();
|
||||||
const x = touch.pageX - rect.left;
|
const x = touch.pageX - rect.left;
|
||||||
const y = touch.pageY - rect.top;
|
const y = touch.pageY - rect.top;
|
||||||
if (this.handleClick(x, y)) {
|
const hc = this.handleClick(x, y, new Set(['Menu']));
|
||||||
|
if (hc) {
|
||||||
|
if (hc != 'Menu') {
|
||||||
this.repDoing = true;
|
this.repDoing = true;
|
||||||
this.debouncedStartClickRepeat(x, y);
|
this.debouncedStartClickRepeat(x, y);
|
||||||
|
} else {
|
||||||
|
this.startTouch = {x, y};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTouchEnd() {
|
onTouchMove(event) {
|
||||||
|
if (this.startTouch) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTouchEnd(event) {
|
||||||
if (!this.$isMobileDevice)
|
if (!this.$isMobileDevice)
|
||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
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() {
|
onTouchCancel() {
|
||||||
@@ -1038,12 +1088,13 @@ class TextPage extends Vue {
|
|||||||
return;
|
return;
|
||||||
this.endClickRepeat();
|
this.endClickRepeat();
|
||||||
if (event.button == 0) {
|
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.repDoing = true;
|
||||||
this.debouncedStartClickRepeat(event.offsetX, event.offsetY);
|
this.debouncedStartClickRepeat(event.offsetX, event.offsetY);
|
||||||
}
|
}
|
||||||
} else if (event.button == 1) {
|
} else if (event.button == 1) {
|
||||||
this.$emit('scrolling-toggle');
|
this.doScrollingToggle();
|
||||||
} else if (event.button == 2) {
|
} else if (event.button == 2) {
|
||||||
this.doToolBarToggle();
|
this.doToolBarToggle();
|
||||||
}
|
}
|
||||||
@@ -1074,7 +1125,7 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick(pointX, pointY) {
|
getClickAction(pointX, pointY) {
|
||||||
const w = pointX/this.realWidth*100;
|
const w = pointX/this.realWidth*100;
|
||||||
const h = pointY/this.realHeight*100;
|
const h = pointY/this.realHeight*100;
|
||||||
|
|
||||||
@@ -1090,6 +1141,13 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick(pointX, pointY, exclude) {
|
||||||
|
const action = this.getClickAction(pointX, pointY);
|
||||||
|
|
||||||
|
if (!exclude || !exclude.has(action)) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'Down' ://Down
|
case 'Down' ://Down
|
||||||
this.doDown();
|
this.doDown();
|
||||||
@@ -1109,8 +1167,9 @@ class TextPage extends Vue {
|
|||||||
default :
|
default :
|
||||||
// Nothing
|
// Nothing
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (action && action != 'Menu');
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import he from 'he';
|
import he from 'he';
|
||||||
import sax from '../../../../server/core/BookConverter/sax';
|
import sax from '../../../../server/core/sax';
|
||||||
import {sleep} from '../../../share/utils';
|
import {sleep} from '../../../share/utils';
|
||||||
|
|
||||||
const maxImageLineCount = 100;
|
const maxImageLineCount = 100;
|
||||||
@@ -67,7 +67,7 @@ export default class BookParser {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const getImageDimensions = (binaryId, binaryType, data) => {
|
const getImageDimensions = (binaryId, binaryType, data) => {
|
||||||
return new Promise (async(resolve, reject) => {
|
return new Promise ((resolve, reject) => { (async() => {
|
||||||
const i = new Image();
|
const i = new Image();
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
i.onload = () => {
|
i.onload = () => {
|
||||||
@@ -81,19 +81,17 @@ export default class BookParser {
|
|||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
i.onerror = (e) => {
|
i.onerror = reject;
|
||||||
reject(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
i.src = `data:${binaryType};base64,${data}`;
|
i.src = `data:${binaryType};base64,${data}`;
|
||||||
await sleep(30*1000);
|
await sleep(30*1000);
|
||||||
if (!resolved)
|
if (!resolved)
|
||||||
reject('Не удалось получить размер изображения');
|
reject('Не удалось получить размер изображения');
|
||||||
});
|
})().catch(reject); });
|
||||||
};
|
};
|
||||||
|
|
||||||
const getExternalImageDimensions = (src) => {
|
const getExternalImageDimensions = (src) => {
|
||||||
return new Promise (async(resolve, reject) => {
|
return new Promise ((resolve, reject) => { (async() => {
|
||||||
const i = new Image();
|
const i = new Image();
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
i.onload = () => {
|
i.onload = () => {
|
||||||
@@ -105,15 +103,13 @@ export default class BookParser {
|
|||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
i.onerror = (e) => {
|
i.onerror = reject;
|
||||||
reject(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
i.src = src;
|
i.src = src;
|
||||||
await sleep(30*1000);
|
await sleep(30*1000);
|
||||||
if (!resolved)
|
if (!resolved)
|
||||||
reject('Не удалось получить размер изображения');
|
reject('Не удалось получить размер изображения');
|
||||||
});
|
})().catch(reject); });
|
||||||
};
|
};
|
||||||
|
|
||||||
const newParagraph = (text, len, addIndex) => {
|
const newParagraph = (text, len, addIndex) => {
|
||||||
@@ -244,6 +240,7 @@ export default class BookParser {
|
|||||||
newParagraph(' ', 1);
|
newParagraph(' ', 1);
|
||||||
isFirstTitlePara = true;
|
isFirstTitlePara = true;
|
||||||
bold = true;
|
bold = true;
|
||||||
|
center = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'epigraph') {
|
if (tag == 'epigraph') {
|
||||||
@@ -286,6 +283,7 @@ export default class BookParser {
|
|||||||
if (tag == 'subtitle') {
|
if (tag == 'subtitle') {
|
||||||
isFirstTitlePara = false;
|
isFirstTitlePara = false;
|
||||||
bold = false;
|
bold = false;
|
||||||
|
center = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag == 'epigraph') {
|
if (tag == 'epigraph') {
|
||||||
@@ -371,11 +369,10 @@ export default class BookParser {
|
|||||||
tClose += (bold ? '</strong>' : '');
|
tClose += (bold ? '</strong>' : '');
|
||||||
tClose += (center ? '</center>' : '');
|
tClose += (center ? '</center>' : '');
|
||||||
|
|
||||||
if (path.indexOf('/fictionbook/body/title') == 0) {
|
if (path.indexOf('/fictionbook/body/title') == 0 ||
|
||||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
path.indexOf('/fictionbook/body/section') == 0 ||
|
||||||
}
|
path.indexOf('/fictionbook/body/epigraph') == 0
|
||||||
|
) {
|
||||||
if (path.indexOf('/fictionbook/body/section') == 0) {
|
|
||||||
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
growParagraph(`${tOpen}${text}${tClose}`, text.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,44 @@
|
|||||||
export const versionHistory = [
|
export const versionHistory = [
|
||||||
|
{
|
||||||
|
showUntil: '2019-11-24',
|
||||||
|
header: '0.7.8 (2019-11-25)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>улучшение html-фильтров для сайтов</li>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
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',
|
showUntil: '2019-10-21',
|
||||||
header: '0.7.5 (2019-10-22)',
|
header: '0.7.5 (2019-10-22)',
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<meta name="description" content="браузерная онлайн-читалка книг из интернета и библиотека">
|
<meta name="description" content="Браузерная онлайн-читалка книг. Поддерживаются форматы: fb2, html, txt, rtf, doc, docx, pdf, epub, mobi.">
|
||||||
<meta name="keywords" content="библиотека,онлайн,читалка,книги,читать,браузер,интернет">
|
<meta name="keywords" content="онлайн,читалка,fb2,книги,читать,браузер,интернет">
|
||||||
<title></title>
|
<title></title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
250
package-lock.json
generated
250
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Liberama",
|
"name": "Liberama",
|
||||||
"version": "0.7.3",
|
"version": "0.7.5",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -541,11 +541,6 @@
|
|||||||
"integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==",
|
"integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==",
|
||||||
"dev": true
|
"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": {
|
"ajv": {
|
||||||
"version": "6.10.2",
|
"version": "6.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||||
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
|
"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": {
|
"are-we-there-yet": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
"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"
|
"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": {
|
"buffer-from": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||||
@@ -2359,6 +2410,51 @@
|
|||||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||||
"dev": true
|
"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": {
|
"compressible": {
|
||||||
"version": "2.0.17",
|
"version": "2.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz",
|
"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": {
|
"create-ecdh": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
|
||||||
@@ -6252,6 +6385,48 @@
|
|||||||
"webpack-sources": "^1.1.0"
|
"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": {
|
"lcid": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
"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": {
|
"lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
|
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
|
||||||
"dev": true
|
"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": {
|
"lodash.uniq": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||||
@@ -7011,11 +7211,15 @@
|
|||||||
"semver": "^5.3.0"
|
"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": {
|
"normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"normalize-url": {
|
"normalize-url": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
@@ -12647,6 +12851,36 @@
|
|||||||
"camelcase": "^5.0.0",
|
"camelcase": "^5.0.0",
|
||||||
"decamelize": "^1.2.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",
|
"name": "Liberama",
|
||||||
"version": "0.7.5",
|
"version": "0.7.8",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
@@ -55,7 +55,6 @@
|
|||||||
"webpack-merge": "^4.2.2"
|
"webpack-merge": "^4.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.4.13",
|
|
||||||
"appcache-webpack-plugin": "^1.4.0",
|
"appcache-webpack-plugin": "^1.4.0",
|
||||||
"axios": "^0.18.1",
|
"axios": "^0.18.1",
|
||||||
"base-x": "^3.0.6",
|
"base-x": "^3.0.6",
|
||||||
@@ -72,6 +71,7 @@
|
|||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
|
"node-stream-zip": "^1.8.2",
|
||||||
"pako": "^1.0.10",
|
"pako": "^1.0.10",
|
||||||
"path-browserify": "^1.0.0",
|
"path-browserify": "^1.0.0",
|
||||||
"safe-buffer": "^5.2.0",
|
"safe-buffer": "^5.2.0",
|
||||||
@@ -83,6 +83,7 @@
|
|||||||
"vue": "github:paulkamer/vue#fix_palemoon_clickhandlers_dist",
|
"vue": "github:paulkamer/vue#fix_palemoon_clickhandlers_dist",
|
||||||
"vue-router": "^3.1.3",
|
"vue-router": "^3.1.3",
|
||||||
"vuex": "^3.1.1",
|
"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`,
|
logDir: `${dataDir}/log`,
|
||||||
publicDir: `${execDir}/public`,
|
publicDir: `${execDir}/public`,
|
||||||
uploadDir: `${execDir}/public/upload`,
|
uploadDir: `${execDir}/public/upload`,
|
||||||
|
sharedDir: `${execDir}/public/shared`,
|
||||||
loggingEnabled: true,
|
loggingEnabled: true,
|
||||||
|
|
||||||
maxUploadFileSize: 50*1024*1024,//50Мб
|
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
|
|
||||||
};
|
|
||||||
@@ -2,5 +2,4 @@ const base = require('./base');
|
|||||||
|
|
||||||
module.exports = Object.assign({}, base, {
|
module.exports = Object.assign({}, base, {
|
||||||
branch: 'development',
|
branch: 'development',
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,23 +1,91 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const utils = require('../core/utils');
|
|
||||||
|
|
||||||
const branchFilename = __dirname + '/application_env';
|
const branchFilename = __dirname + '/application_env';
|
||||||
|
|
||||||
let branch = 'production';
|
const propsToSave = [
|
||||||
try {
|
'maxUploadFileSize',
|
||||||
fs.accessSync(branchFilename);
|
'maxTempPublicDirSize',
|
||||||
branch = fs.readFileSync(branchFilename, 'utf8').trim();
|
'maxUploadPublicDirSize',
|
||||||
} catch (err) {
|
'useExternalBookConverter',
|
||||||
|
|
||||||
|
'servers',
|
||||||
|
];
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
//singleton
|
||||||
|
class ConfigManager {
|
||||||
|
constructor() {
|
||||||
|
if (!instance) {
|
||||||
|
this.inited = false;
|
||||||
|
|
||||||
|
instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env.NODE_ENV = branch;
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
const confFilename = __dirname + `/${branch}.js`;
|
async init() {
|
||||||
|
if (this.inited)
|
||||||
|
throw new Error('already inited');
|
||||||
|
|
||||||
fs.accessSync(confFilename);
|
this.branch = 'production';
|
||||||
|
try {
|
||||||
|
await fs.access(branchFilename);
|
||||||
|
this.branch = (await fs.readFile(branchFilename, 'utf8')).trim();
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
const config = require(confFilename);
|
process.env.NODE_ENV = this.branch;
|
||||||
|
|
||||||
//fs.ensureDirSync(config.dataDir);
|
this.branchConfigFile = __dirname + `/${this.branch}.js`;
|
||||||
|
await fs.access(this.branchConfigFile);
|
||||||
|
this._config = require(this.branchConfigFile);
|
||||||
|
|
||||||
module.exports = config;
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ConfigManager;
|
||||||
@@ -11,6 +11,7 @@ module.exports = Object.assign({}, base, {
|
|||||||
logDir: `${dataDir}/log`,
|
logDir: `${dataDir}/log`,
|
||||||
publicDir: `${execDir}/public`,
|
publicDir: `${execDir}/public`,
|
||||||
uploadDir: `${execDir}/public/upload`,
|
uploadDir: `${execDir}/public/upload`,
|
||||||
|
sharedDir: `${execDir}/public/shared`,
|
||||||
|
|
||||||
servers: [
|
servers: [
|
||||||
{
|
{
|
||||||
@@ -21,5 +22,4 @@ module.exports = Object.assign({}, base, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const BaseController = require('./BaseController');
|
const BaseController = require('./BaseController');
|
||||||
const log = require('../core/getLogger').getLog();
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
class MiscController extends BaseController {
|
class MiscController extends BaseController {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
const BaseController = require('./BaseController');
|
const BaseController = require('./BaseController');
|
||||||
const ReaderWorker = require('../core/ReaderWorker');
|
const ReaderWorker = require('../core/Reader/ReaderWorker');//singleton
|
||||||
const readerStorage = require('../core/readerStorage');
|
const ReaderStorage = require('../core/Reader/ReaderStorage');//singleton
|
||||||
const workerState = require('../core/workerState');
|
const WorkerState = require('../core/WorkerState');//singleton
|
||||||
|
|
||||||
class ReaderController extends BaseController {
|
class ReaderController extends BaseController {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
super(config);
|
super(config);
|
||||||
|
this.readerStorage = new ReaderStorage();
|
||||||
this.readerWorker = new ReaderWorker(config);
|
this.readerWorker = new ReaderWorker(config);
|
||||||
|
this.workerState = new WorkerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadBook(req, res) {
|
async loadBook(req, res) {
|
||||||
@@ -19,7 +21,7 @@ class ReaderController extends BaseController {
|
|||||||
url: request.url,
|
url: request.url,
|
||||||
enableSitesFilter: (request.hasOwnProperty('enableSitesFilter') ? request.enableSitesFilter : true)
|
enableSitesFilter: (request.hasOwnProperty('enableSitesFilter') ? request.enableSitesFilter : true)
|
||||||
});
|
});
|
||||||
const state = workerState.getState(workerId);
|
const state = this.workerState.getState(workerId);
|
||||||
return (state ? state : {});
|
return (state ? state : {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e.message;
|
error = e.message;
|
||||||
@@ -38,7 +40,7 @@ class ReaderController extends BaseController {
|
|||||||
if (!request.items || Array.isArray(request.data))
|
if (!request.items || Array.isArray(request.data))
|
||||||
throw new Error(`key 'items' is empty`);
|
throw new Error(`key 'items' is empty`);
|
||||||
|
|
||||||
return await readerStorage.doAction(request);
|
return await this.readerStorage.doAction(request);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e.message;
|
error = e.message;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
const BaseController = require('./BaseController');
|
const BaseController = require('./BaseController');
|
||||||
const workerState = require('../core/workerState');
|
const WorkerState = require('../core/WorkerState');//singleton
|
||||||
|
|
||||||
class WorkerController extends BaseController {
|
class WorkerController extends BaseController {
|
||||||
|
constructor(config) {
|
||||||
|
super(config);
|
||||||
|
this.workerState = new WorkerState();
|
||||||
|
}
|
||||||
|
|
||||||
async getState(req, res) {
|
async getState(req, res) {
|
||||||
const request = req.body;
|
const request = req.body;
|
||||||
let error = '';
|
let error = '';
|
||||||
@@ -9,7 +14,7 @@ class WorkerController extends BaseController {
|
|||||||
if (!request.workerId)
|
if (!request.workerId)
|
||||||
throw new Error(`key 'workerId' is wrong`);
|
throw new Error(`key 'workerId' is wrong`);
|
||||||
|
|
||||||
const state = workerState.getState(request.workerId);
|
const state = this.workerState.getState(request.workerId);
|
||||||
return (state ? state : {});
|
return (state ? state : {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e.message;
|
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 fs = require('fs-extra');
|
||||||
const zlib = require('zlib');
|
const zlib = require('zlib');
|
||||||
const crypto = require('crypto');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const unbzip2Stream = require('unbzip2-stream');
|
const unbzip2Stream = require('unbzip2-stream');
|
||||||
const tar = require('tar-fs');
|
const tar = require('tar-fs');
|
||||||
const AdmZip = require('adm-zip');
|
const ZipStreamer = require('./ZipStreamer');
|
||||||
|
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const FileDetector = require('./FileDetector');
|
const FileDetector = require('./FileDetector');
|
||||||
@@ -112,18 +111,8 @@ class FileDecompressor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async unZip(filename, outputDir) {
|
async unZip(filename, outputDir) {
|
||||||
return new Promise((resolve) => {
|
const zip = new ZipStreamer();
|
||||||
const files = [];
|
return await zip.unpack(filename, outputDir);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unBz2(filename, outputDir) {
|
unBz2(filename, outputDir) {
|
||||||
@@ -163,7 +152,7 @@ class FileDecompressor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
decompressByStream(stream, filename, outputDir) {
|
decompressByStream(stream, filename, outputDir) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise((resolve, reject) => { (async() => {
|
||||||
const file = {path: path.parse(filename).name};
|
const file = {path: path.parse(filename).name};
|
||||||
let outFilename = `${outputDir}/${file.path}`;
|
let outFilename = `${outputDir}/${file.path}`;
|
||||||
if (await fs.pathExists(outFilename)) {
|
if (await fs.pathExists(outFilename)) {
|
||||||
@@ -183,20 +172,12 @@ class FileDecompressor {
|
|||||||
resolve([file]);
|
resolve([file]);
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.on('error', (err) => {
|
stream.on('error', reject);
|
||||||
reject(err);
|
inputStream.on('error', reject);
|
||||||
});
|
outputStream.on('error', reject);
|
||||||
|
|
||||||
inputStream.on('error', (err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
outputStream.on('error', (err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
inputStream.pipe(stream).pipe(outputStream);
|
inputStream.pipe(stream).pipe(outputStream);
|
||||||
});
|
})().catch(reject); });
|
||||||
}
|
}
|
||||||
|
|
||||||
async gzipBuffer(buf) {
|
async gzipBuffer(buf) {
|
||||||
@@ -208,15 +189,26 @@ class FileDecompressor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async gzipFileIfNotExists(filename, outDir) {
|
async gzipFile(inputFile, outputFile) {
|
||||||
const buf = await fs.readFile(filename);
|
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}`;
|
const outFilename = `${outDir}/${hash}`;
|
||||||
|
|
||||||
if (!await fs.pathExists(outFilename)) {
|
if (!await fs.pathExists(outFilename)) {
|
||||||
await fs.writeFile(outFilename, await this.gzipBuffer(buf))
|
await this.gzipFile(filename, outFilename);
|
||||||
} else {
|
} else {
|
||||||
await utils.touchFile(outFilename);
|
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 he = require('he');
|
||||||
|
|
||||||
const textUtils = require('./textUtils');
|
const textUtils = require('./textUtils');
|
||||||
const utils = require('../utils');
|
const utils = require('../../utils');
|
||||||
|
|
||||||
let execConverterCounter = 0;
|
let execConverterCounter = 0;
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ class ConvertBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
escapeEntities(text) {
|
escapeEntities(text) {
|
||||||
return he.escape(he.decode(text));
|
return he.escape(he.decode(text.replace(/ /g, ' ')));
|
||||||
}
|
}
|
||||||
|
|
||||||
formatFb2(fb2) {
|
formatFb2(fb2) {
|
||||||
@@ -23,7 +23,7 @@ class ConvertFb2 extends ConvertBase {
|
|||||||
const right = data.indexOf('?>', left);
|
const right = data.indexOf('?>', left);
|
||||||
if (right >= 0) {
|
if (right >= 0) {
|
||||||
const head = data.slice(left, right + 2).toString();
|
const head = data.slice(left, right + 2).toString();
|
||||||
const m = head.match(/encoding="(.*)"/);
|
const m = head.match(/encoding="(.*?)"/);
|
||||||
if (m) {
|
if (m) {
|
||||||
let encoding = m[1].toLowerCase();
|
let encoding = m[1].toLowerCase();
|
||||||
if (encoding != 'utf-8') {
|
if (encoding != 'utf-8') {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
const ConvertBase = require('./ConvertBase');
|
const ConvertBase = require('./ConvertBase');
|
||||||
const sax = require('./sax');
|
const sax = require('../../sax');
|
||||||
const textUtils = require('./textUtils');
|
const textUtils = require('./textUtils');
|
||||||
|
|
||||||
class ConvertHtml extends ConvertBase {
|
class ConvertHtml extends ConvertBase {
|
||||||
@@ -39,16 +39,19 @@ class ConvertHtml extends ConvertBase {
|
|||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
let inTitle = false;
|
let inTitle = false;
|
||||||
|
let inSubTitle = false;
|
||||||
let inImage = false;
|
let inImage = false;
|
||||||
let image = {};
|
let image = {};
|
||||||
let bold = false;
|
let bold = false;
|
||||||
let italic = false;
|
let italic = false;
|
||||||
|
let begining = true;
|
||||||
|
|
||||||
let spaceCounter = [];
|
let spaceCounter = [];
|
||||||
|
|
||||||
const repCrLfTab = (text) => text.replace(/[\n\r]/g, '').replace(/\t/g, ' ');
|
const repCrLfTab = (text) => text.replace(/[\n\r]/g, '').replace(/\t/g, ' ');
|
||||||
|
|
||||||
const newParagraph = () => {
|
const newParagraph = () => {
|
||||||
|
begining = false;
|
||||||
pars.push({_n: 'p', _t: ''});
|
pars.push({_n: 'p', _t: ''});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,12 +61,15 @@ class ConvertHtml extends ConvertBase {
|
|||||||
|
|
||||||
const l = pars.length;
|
const l = pars.length;
|
||||||
pars[l - 1]._t += text;
|
pars[l - 1]._t += text;
|
||||||
|
if (inSubTitle)
|
||||||
|
pars[l - 1]._n = '';
|
||||||
|
|
||||||
//посчитаем отступы у текста, чтобы выделить потом параграфы
|
//посчитаем отступы у текста, чтобы выделить потом параграфы
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
for (let line of lines) {
|
for (let line of lines) {
|
||||||
if (line.trim() == '')
|
if (line.trim() == '')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
line = repCrLfTab(line);
|
line = repCrLfTab(line);
|
||||||
|
|
||||||
let l = 0;
|
let l = 0;
|
||||||
@@ -76,16 +82,21 @@ class ConvertHtml extends ConvertBase {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const newPara = new Set(['tr', '/table', 'hr', 'br', 'br/', 'li', 'dt', 'dd', 'p', 'title', '/title', 'h1', 'h2', 'h3', '/h1', '/h2', '/h3']);
|
const newPara = new Set(['tr', '/table', 'hr', 'br', 'br/', 'li', 'dt', 'dd', 'p', 'title', '/title', 'ul', '/ul', 'h1', 'h2', 'h3', 'h4', 'h5', '/h1', '/h2', '/h3', '/h4', '/h5']);
|
||||||
|
const newPara2 = new Set(['h1', 'h2', 'h3', 'h4', 'h5']);
|
||||||
|
|
||||||
const onTextNode = (text, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
|
const onTextNode = (text, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
|
||||||
text = this.escapeEntities(text);
|
text = this.escapeEntities(text);
|
||||||
|
|
||||||
if (!cutCounter && !(cutTitle && inTitle)) {
|
if (!cutCounter && !(cutTitle && inTitle)) {
|
||||||
let tOpen = (bold ? '<strong>' : '');
|
let tOpen = '';
|
||||||
|
tOpen += (inSubTitle ? '<subtitle>' : '');
|
||||||
|
tOpen += (bold ? '<strong>' : '');
|
||||||
tOpen += (italic ? '<emphasis>' : '');
|
tOpen += (italic ? '<emphasis>' : '');
|
||||||
let tClose = (italic ? '</emphasis>' : '');
|
let tClose = ''
|
||||||
|
tClose += (italic ? '</emphasis>' : '');
|
||||||
tClose += (bold ? '</strong>' : '');
|
tClose += (bold ? '</strong>' : '');
|
||||||
|
tClose += (inSubTitle ? '</subtitle>' : '');
|
||||||
|
|
||||||
growParagraph(`${tOpen}${text}${tClose}`);
|
growParagraph(`${tOpen}${text}${tClose}`);
|
||||||
}
|
}
|
||||||
@@ -105,6 +116,8 @@ class ConvertHtml extends ConvertBase {
|
|||||||
|
|
||||||
const onStartNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
|
const onStartNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
|
||||||
if (!cutCounter) {
|
if (!cutCounter) {
|
||||||
|
if (newPara2.has(tag) && !begining)
|
||||||
|
newParagraph();
|
||||||
if (newPara.has(tag))
|
if (newPara.has(tag))
|
||||||
newParagraph();
|
newParagraph();
|
||||||
|
|
||||||
@@ -129,6 +142,10 @@ class ConvertHtml extends ConvertBase {
|
|||||||
cutTitle = true;
|
cutTitle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tag == 'subtitle') {
|
||||||
|
inSubTitle = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (tag == 'fb2-image') {
|
if (tag == 'fb2-image') {
|
||||||
inImage = true;
|
inImage = true;
|
||||||
const attrs = sax.getAttrsSync(tail);
|
const attrs = sax.getAttrsSync(tail);
|
||||||
@@ -140,6 +157,8 @@ class ConvertHtml extends ConvertBase {
|
|||||||
if (!cutCounter) {
|
if (!cutCounter) {
|
||||||
if (newPara.has('/' + tag))
|
if (newPara.has('/' + tag))
|
||||||
newParagraph();
|
newParagraph();
|
||||||
|
if (newPara2.has('/' + tag))
|
||||||
|
newParagraph();
|
||||||
|
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case 'i':
|
case 'i':
|
||||||
@@ -159,6 +178,9 @@ class ConvertHtml extends ConvertBase {
|
|||||||
if (tag == 'title' || tag == 'cut-title')
|
if (tag == 'title' || tag == 'cut-title')
|
||||||
inTitle = false;
|
inTitle = false;
|
||||||
|
|
||||||
|
if (tag == 'subtitle')
|
||||||
|
inSubTitle = false;
|
||||||
|
|
||||||
if (tag == 'fb2-image')
|
if (tag == 'fb2-image')
|
||||||
inImage = false;
|
inImage = false;
|
||||||
};
|
};
|
||||||
@@ -171,7 +193,6 @@ class ConvertHtml extends ConvertBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
titleInfo['book-title'] = title;
|
titleInfo['book-title'] = title;
|
||||||
|
|
||||||
//подозрение на чистый текст, надо разбить на параграфы
|
//подозрение на чистый текст, надо разбить на параграфы
|
||||||
if (isText || pars.length < buf.length/2000) {
|
if (isText || pars.length < buf.length/2000) {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
@@ -197,7 +218,8 @@ class ConvertHtml extends ConvertBase {
|
|||||||
while (i > 0 && (!spaceCounter[i] || spaceCounter[i] < total)) i--;
|
while (i > 0 && (!spaceCounter[i] || spaceCounter[i] < total)) i--;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parIndent = (i > 0 ? i : 0);
|
let parIndent = (i > 0 ? i : 0);
|
||||||
|
if (parIndent > 2) parIndent--;
|
||||||
|
|
||||||
let newPars = [];
|
let newPars = [];
|
||||||
const newPar = () => {
|
const newPar = () => {
|
||||||
@@ -233,7 +255,7 @@ class ConvertHtml extends ConvertBase {
|
|||||||
l++;
|
l++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (l >= parIndent) {
|
if (l >= parIndent || line == '') {
|
||||||
if (j > 0)
|
if (j > 0)
|
||||||
newPar();
|
newPar();
|
||||||
j++;
|
j++;
|
||||||
@@ -250,6 +272,7 @@ class ConvertHtml extends ConvertBase {
|
|||||||
//убираем лишнее, делаем валидный fb2, т.к. в рез-те разбиения на параграфы бьются теги
|
//убираем лишнее, делаем валидный fb2, т.к. в рез-те разбиения на параграфы бьются теги
|
||||||
bold = false;
|
bold = false;
|
||||||
italic = false;
|
italic = false;
|
||||||
|
inSubTitle = false;
|
||||||
pars = body.section._a[0];
|
pars = body.section._a[0];
|
||||||
for (let i = 0; i < pars.length; i++) {
|
for (let i = 0; i < pars.length; i++) {
|
||||||
if (pars[i]._n != 'p')
|
if (pars[i]._n != 'p')
|
||||||
@@ -257,17 +280,26 @@ class ConvertHtml extends ConvertBase {
|
|||||||
|
|
||||||
pars[i]._t = this.repSpaces(pars[i]._t).trim();
|
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;
|
const t = pars[i]._t;
|
||||||
|
let first = true;
|
||||||
|
|
||||||
let a = [];
|
let a = [];
|
||||||
|
|
||||||
const onTextNode = (text) => {
|
const onTextNode = (text) => {
|
||||||
let tOpen = (bold ? '<strong>' : '');
|
let tOpen = '';
|
||||||
|
tOpen += (inSubTitle ? '<subtitle>' : '');
|
||||||
|
tOpen += (bold ? '<strong>' : '');
|
||||||
tOpen += (italic ? '<emphasis>' : '');
|
tOpen += (italic ? '<emphasis>' : '');
|
||||||
let tClose = (italic ? '</emphasis>' : '');
|
let tClose = ''
|
||||||
|
tClose += (italic ? '</emphasis>' : '');
|
||||||
tClose += (bold ? '</strong>' : '');
|
tClose += (bold ? '</strong>' : '');
|
||||||
|
tClose += (inSubTitle ? '</subtitle>' : '');
|
||||||
|
|
||||||
|
if (first)
|
||||||
|
text = text.replace(/^\s+/, ''); //trimLeft
|
||||||
a.push(`${tOpen}${text}${tClose}`);
|
a.push(`${tOpen}${text}${tClose}`);
|
||||||
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onStartNode = (tag) => {
|
const onStartNode = (tag) => {
|
||||||
@@ -275,6 +307,8 @@ class ConvertHtml extends ConvertBase {
|
|||||||
bold = true;
|
bold = true;
|
||||||
if (tag == 'emphasis')
|
if (tag == 'emphasis')
|
||||||
italic = true;
|
italic = true;
|
||||||
|
if (tag == 'subtitle')
|
||||||
|
inSubTitle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onEndNode = (tag) => {
|
const onEndNode = (tag) => {
|
||||||
@@ -282,6 +316,8 @@ class ConvertHtml extends ConvertBase {
|
|||||||
bold = false;
|
bold = false;
|
||||||
if (tag == 'emphasis')
|
if (tag == 'emphasis')
|
||||||
italic = false;
|
italic = false;
|
||||||
|
if (tag == 'subtitle')
|
||||||
|
inSubTitle = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sax.parseSync(t, { onStartNode, onEndNode, onTextNode });
|
sax.parseSync(t, { onStartNode, onEndNode, onTextNode });
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const sax = require('./sax');
|
const sax = require('../../sax');
|
||||||
const utils = require('../utils');
|
const utils = require('../../utils');
|
||||||
const ConvertHtml = require('./ConvertHtml');
|
const ConvertHtml = require('./ConvertHtml');
|
||||||
|
|
||||||
class ConvertPdf extends ConvertHtml {
|
class ConvertPdf extends ConvertHtml {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const URL = require('url').URL;
|
const URL = require('url').URL;
|
||||||
|
|
||||||
const sax = require('./sax');
|
const sax = require('../../sax');
|
||||||
const ConvertBase = require('./ConvertBase');
|
const ConvertBase = require('./ConvertBase');
|
||||||
|
|
||||||
class ConvertSamlib extends ConvertBase {
|
class ConvertSamlib extends ConvertBase {
|
||||||
@@ -102,6 +102,8 @@ class ConvertSamlib extends ConvertBase {
|
|||||||
case 'h1':
|
case 'h1':
|
||||||
case 'h2':
|
case 'h2':
|
||||||
case 'h3':
|
case 'h3':
|
||||||
|
case 'h4':
|
||||||
|
case 'h5':
|
||||||
if (inPara)
|
if (inPara)
|
||||||
closeTag('p');
|
closeTag('p');
|
||||||
openTag('p');
|
openTag('p');
|
||||||
@@ -173,6 +175,8 @@ class ConvertSamlib extends ConvertBase {
|
|||||||
case 'h1':
|
case 'h1':
|
||||||
case 'h2':
|
case 'h2':
|
||||||
case 'h3':
|
case 'h3':
|
||||||
|
case 'h4':
|
||||||
|
case 'h5':
|
||||||
closeTag('p');
|
closeTag('p');
|
||||||
bold = false;
|
bold = false;
|
||||||
break;
|
break;
|
||||||
@@ -12,7 +12,10 @@ const sitesFilter = {
|
|||||||
converter: 'cutter',
|
converter: 'cutter',
|
||||||
begin: `<!-- BEGIN section where work skin applies -->`,
|
begin: `<!-- BEGIN section where work skin applies -->`,
|
||||||
end: `<!-- END work skin -->`,
|
end: `<!-- END work skin -->`,
|
||||||
}
|
},
|
||||||
|
'flibusta.is': {
|
||||||
|
converter: 'flibusta'
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConvertSites extends ConvertHtml {
|
class ConvertSites extends ConvertHtml {
|
||||||
@@ -54,11 +57,11 @@ class ConvertSites extends ConvertHtml {
|
|||||||
if (m)
|
if (m)
|
||||||
title = m[1];
|
title = m[1];
|
||||||
|
|
||||||
return `<title>${title.trim()}</title>`;
|
return title.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
cutter(text, opts) {
|
cutter(text, opts) {
|
||||||
const title = this.getTitle(text);
|
const title = `<title>${this.getTitle(text)}</title>`;
|
||||||
const l = text.indexOf(opts.begin) + opts.begin.length;
|
const l = text.indexOf(opts.begin) + opts.begin.length;
|
||||||
const r = text.indexOf(opts.end);
|
const r = text.indexOf(opts.end);
|
||||||
if (l < 0 || r < 0 || r <= l)
|
if (l < 0 || r < 0 || r <= l)
|
||||||
@@ -66,6 +69,42 @@ class ConvertSites extends ConvertHtml {
|
|||||||
|
|
||||||
return text.substring(l, r) + title;
|
return text.substring(l, r) + title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flibusta(text) {
|
||||||
|
let author = '';
|
||||||
|
let m = text.match(/- <a href=".+">([\s\S]*?)<\/a><br\/?>/);
|
||||||
|
if (m)
|
||||||
|
author = m[1];
|
||||||
|
|
||||||
|
let book = this.getTitle(text);
|
||||||
|
book = book.replace(' (fb2) | Флибуста', '');
|
||||||
|
|
||||||
|
const title = `<title>${author}${(author ? ' - ' : '')}${book}</title>`;
|
||||||
|
|
||||||
|
let begin = '<h3 class="book">';
|
||||||
|
if (text.indexOf(begin) <= 0)
|
||||||
|
begin = '<h3 class=book>';
|
||||||
|
|
||||||
|
const end = '<div id="footer">';
|
||||||
|
|
||||||
|
const l = text.indexOf(begin);
|
||||||
|
const r = text.indexOf(end);
|
||||||
|
if (l < 0 || r < 0 || r <= l)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return text.substring(l, r)
|
||||||
|
.replace(/blockquote class="?book"?/g, 'p')
|
||||||
|
.replace(/<br\/?>\s*<\/h3>/g, '</h3>')
|
||||||
|
.replace(/<h3 class="?book"?>/g, '<br><br><subtitle>')
|
||||||
|
.replace(/<h5 class="?book"?>/g, '<br><br><subtitle>')
|
||||||
|
.replace(/<h3>/g, '<br><br><subtitle>')
|
||||||
|
.replace(/<h5>/g, '<br><br><subtitle>')
|
||||||
|
.replace(/<\/h3>/g, '</subtitle><br>')
|
||||||
|
.replace(/<\/h5>/g, '</subtitle><br>')
|
||||||
|
.replace(/<div class="?stanza"?>/g, '<br>')
|
||||||
|
.replace(/<div>/g, '<br>')
|
||||||
|
+ title;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ConvertSites;
|
module.exports = ConvertSites;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const FileDetector = require('../FileDetector');
|
const FileDetector = require('../../FileDetector');
|
||||||
|
|
||||||
//порядок важен
|
//порядок важен
|
||||||
const convertClassFactory = [
|
const convertClassFactory = [
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
const SQL = require('sql-template-strings');
|
const SQL = require('sql-template-strings');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const connManager = require('../db/connManager');
|
const ConnManager = require('../../db/ConnManager');//singleton
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
//singleton
|
||||||
class ReaderStorage {
|
class ReaderStorage {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.storagePool = connManager.pool.readerStorage;
|
if (!instance) {
|
||||||
|
this.connManager = new ConnManager();
|
||||||
|
this.storagePool = this.connManager.pool.readerStorage;
|
||||||
this.periodicCleanCache(3*3600*1000);//1 раз в 3 часа
|
this.periodicCleanCache(3*3600*1000);//1 раз в 3 часа
|
||||||
|
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doAction(act) {
|
async doAction(act) {
|
||||||
@@ -113,6 +123,4 @@ class ReaderStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const readerStorage = new ReaderStorage();
|
module.exports = ReaderStorage;
|
||||||
|
|
||||||
module.exports = readerStorage;
|
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
const workerState = require('./workerState');
|
const WorkerState = require('../WorkerState');//singleton
|
||||||
const FileDownloader = require('./FileDownloader');
|
const FileDownloader = require('../FileDownloader');
|
||||||
const FileDecompressor = require('./FileDecompressor');
|
const FileDecompressor = require('../FileDecompressor');
|
||||||
const BookConverter = require('./BookConverter');
|
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 {
|
class ReaderWorker {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
|
if (!instance) {
|
||||||
this.config = Object.assign({}, config);
|
this.config = Object.assign({}, config);
|
||||||
|
|
||||||
this.config.tempDownloadDir = `${config.tempDir}/download`;
|
this.config.tempDownloadDir = `${config.tempDir}/download`;
|
||||||
@@ -21,15 +23,18 @@ class ReaderWorker {
|
|||||||
this.config.tempPublicDir = `${config.publicDir}/tmp`;
|
this.config.tempPublicDir = `${config.publicDir}/tmp`;
|
||||||
fs.ensureDirSync(this.config.tempPublicDir);
|
fs.ensureDirSync(this.config.tempPublicDir);
|
||||||
|
|
||||||
|
this.workerState = new WorkerState();
|
||||||
this.down = new FileDownloader();
|
this.down = new FileDownloader();
|
||||||
this.decomp = new FileDecompressor();
|
this.decomp = new FileDecompressor();
|
||||||
this.bookConverter = new BookConverter(this.config);
|
this.bookConverter = new BookConverter(this.config);
|
||||||
|
|
||||||
if (!singleCleanExecute) {
|
|
||||||
this.periodicCleanDir(this.config.tempPublicDir, this.config.maxTempPublicDirSize, 60*60*1000);//1 раз в час
|
this.periodicCleanDir(this.config.tempPublicDir, this.config.maxTempPublicDirSize, 60*60*1000);//1 раз в час
|
||||||
this.periodicCleanDir(this.config.uploadDir, this.config.maxUploadPublicDirSize, 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) {
|
async loadBook(opts, wState) {
|
||||||
@@ -69,8 +74,7 @@ class ReaderWorker {
|
|||||||
try {
|
try {
|
||||||
decompFiles = await this.decomp.decompressNested(downloadedFilename, decompDir);
|
decompFiles = await this.decomp.decompressNested(downloadedFilename, decompDir);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (this.config.branch == 'development')
|
log(LM_ERR, e.stack);
|
||||||
console.error(e);
|
|
||||||
throw new Error('Ошибка распаковки');
|
throw new Error('Ошибка распаковки');
|
||||||
}
|
}
|
||||||
wState.set({progress: 100});
|
wState.set({progress: 100});
|
||||||
@@ -92,8 +96,7 @@ class ReaderWorker {
|
|||||||
wState.finish({path: `/tmp/${finishFilename}`});
|
wState.finish({path: `/tmp/${finishFilename}`});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (this.config.branch == 'development')
|
log(LM_ERR, e.stack);
|
||||||
console.error(e);
|
|
||||||
wState.set({state: 'error', error: (errMes ? errMes : e.message)});
|
wState.set({state: 'error', error: (errMes ? errMes : e.message)});
|
||||||
} finally {
|
} finally {
|
||||||
//clean
|
//clean
|
||||||
@@ -107,8 +110,8 @@ class ReaderWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadBookUrl(opts) {
|
loadBookUrl(opts) {
|
||||||
const workerId = workerState.generateWorkerId();
|
const workerId = this.workerState.generateWorkerId();
|
||||||
const wState = workerState.getControl(workerId);
|
const wState = this.workerState.getControl(workerId);
|
||||||
wState.set({state: 'start'});
|
wState.set({state: 'start'});
|
||||||
|
|
||||||
this.loadBook(opts, wState);
|
this.loadBook(opts, wState);
|
||||||
@@ -117,10 +120,7 @@ class ReaderWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveFile(file) {
|
async saveFile(file) {
|
||||||
const buf = await fs.readFile(file.path);
|
const hash = await utils.getFileHash(file.path, 'sha256', 'hex');
|
||||||
|
|
||||||
const hash = crypto.createHash('sha256').update(buf).digest('hex');
|
|
||||||
|
|
||||||
const outFilename = `${this.config.uploadDir}/${hash}`;
|
const outFilename = `${this.config.uploadDir}/${hash}`;
|
||||||
|
|
||||||
if (!await fs.pathExists(outFilename)) {
|
if (!await fs.pathExists(outFilename)) {
|
||||||
@@ -135,7 +135,6 @@ class ReaderWorker {
|
|||||||
|
|
||||||
async periodicCleanDir(dir, maxSize, timeout) {
|
async periodicCleanDir(dir, maxSize, timeout) {
|
||||||
try {
|
try {
|
||||||
log(`Start clean dir: ${dir}, maxSize=${maxSize}`);
|
|
||||||
const list = await fs.readdir(dir);
|
const list = await fs.readdir(dir);
|
||||||
|
|
||||||
let size = 0;
|
let size = 0;
|
||||||
@@ -147,21 +146,20 @@ class ReaderWorker {
|
|||||||
files.push({name, stat});
|
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);
|
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (i < files.length && size > maxSize) {
|
while (i < files.length && size > maxSize) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
log(`rm ${dir}/${file.name}`);
|
|
||||||
await fs.remove(`${dir}/${file.name}`);
|
await fs.remove(`${dir}/${file.name}`);
|
||||||
size -= file.stat.size;
|
size -= file.stat.size;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
log(`removed ${i} files`);
|
log(`removed ${i} files`);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
log(LM_ERR, e.message);
|
log(LM_ERR, e.stack);
|
||||||
} finally {
|
} finally {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.periodicCleanDir(dir, maxSize, timeout);
|
this.periodicCleanDir(dir, maxSize, timeout);
|
||||||
@@ -3,10 +3,18 @@ const utils = require('./utils');
|
|||||||
const cleanInterval = 3600; //sec
|
const cleanInterval = 3600; //sec
|
||||||
const cleanAfterLastModified = cleanInterval - 60; //sec
|
const cleanAfterLastModified = cleanInterval - 60; //sec
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
//singleton
|
||||||
class WorkerState {
|
class WorkerState {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
if (!instance) {
|
||||||
this.states = {};
|
this.states = {};
|
||||||
setTimeout(this.cleanStates.bind(this), cleanInterval*1000);
|
this.cleanStates();
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
generateWorkerId() {
|
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 { spawn } = require('child_process');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const crypto = require('crypto');
|
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) {
|
function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
@@ -20,7 +42,7 @@ function spawnProcess(cmd, opts) {
|
|||||||
onData = (onData ? onData : () => {});
|
onData = (onData ? onData : () => {});
|
||||||
args = (args ? args : []);
|
args = (args ? args : []);
|
||||||
|
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise((resolve, reject) => { (async() => {
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
const proc = spawn(cmd, args, {detached: true});
|
const proc = spawn(cmd, args, {detached: true});
|
||||||
|
|
||||||
@@ -50,10 +72,13 @@ function spawnProcess(cmd, opts) {
|
|||||||
process.kill(proc.pid);
|
process.kill(proc.pid);
|
||||||
reject({status: 'killed', stdout, stderr});
|
reject({status: 'killed', stdout, stderr});
|
||||||
}
|
}
|
||||||
});
|
})().catch(reject); });
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
toBase36,
|
||||||
|
fromBase36,
|
||||||
|
getFileHash,
|
||||||
sleep,
|
sleep,
|
||||||
randomHexString,
|
randomHexString,
|
||||||
touchFile,
|
touchFile,
|
||||||
|
|||||||
@@ -1,20 +1,30 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
const SqliteConnectionPool = require('./SqliteConnectionPool');
|
const SqliteConnectionPool = require('./SqliteConnectionPool');
|
||||||
const log = require('../core/getLogger').getLog();
|
const log = new (require('../core/AppLogger'))().log;//singleton
|
||||||
|
|
||||||
const migrations = {
|
const migrations = {
|
||||||
'app': require('./migrations/app'),
|
'app': require('./migrations/app'),
|
||||||
'readerStorage': require('./migrations/readerStorage'),
|
'readerStorage': require('./migrations/readerStorage'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
//singleton
|
||||||
class ConnManager {
|
class ConnManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._pool = {};
|
if (!instance) {
|
||||||
|
this.inited = false;
|
||||||
|
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(config) {
|
async init(config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
this._pool = {};
|
||||||
|
|
||||||
const force = null;//(config.branch == 'development' ? 'last' : null);
|
const force = null;//(config.branch == 'development' ? 'last' : null);
|
||||||
|
|
||||||
@@ -39,6 +49,7 @@ class ConnManager {
|
|||||||
|
|
||||||
this._pool[poolConfig.poolName] = connPool;
|
this._pool[poolConfig.poolName] = connPool;
|
||||||
}
|
}
|
||||||
|
this.inited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get pool() {
|
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) {
|
function webpackDevMiddleware(app) {
|
||||||
const webpack = require('webpack');
|
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 fs = require('fs-extra');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const argv = require('minimist')(process.argv.slice(2));
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
|
|
||||||
const connManager = require('./db/connManager');
|
|
||||||
|
|
||||||
async function init() {
|
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.dataDir);
|
||||||
await fs.ensureDir(config.uploadDir);
|
await fs.ensureDir(config.uploadDir);
|
||||||
|
await fs.ensureDir(config.sharedDir);
|
||||||
|
|
||||||
await fs.ensureDir(config.tempDir);
|
await fs.ensureDir(config.tempDir);
|
||||||
await fs.emptyDir(config.tempDir);
|
await fs.emptyDir(config.tempDir);
|
||||||
|
|
||||||
@@ -26,16 +35,14 @@ async function init() {
|
|||||||
await fs.move(appNewDir, appDir);
|
await fs.move(appNewDir, appDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
//загружаем конфиг из файла
|
//connections
|
||||||
await configSaver.load(config, argv.config);
|
const connManager = new (require('./db/ConnManager'))();//singleton
|
||||||
|
await connManager.init(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
log(`${config.name} v${config.version}`);
|
const log = new (require('./core/AppLogger'))().log;//singleton
|
||||||
log('Initializing');
|
const config = new (require('./config'))().config;//singleton
|
||||||
await init();
|
|
||||||
|
|
||||||
await connManager.init(config);
|
|
||||||
|
|
||||||
//servers
|
//servers
|
||||||
for (let server of config.servers) {
|
for (let server of config.servers) {
|
||||||
@@ -86,6 +93,7 @@ async function main() {
|
|||||||
|
|
||||||
(async() => {
|
(async() => {
|
||||||
try {
|
try {
|
||||||
|
await init();
|
||||||
await main();
|
await main();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
Reference in New Issue
Block a user