Compare commits

...

60 Commits

Author SHA1 Message Date
Book Pauk
be0f6e57d7 Merge branch 'release/0.7.7b' 2019-11-13 19:42:21 +07:00
Book Pauk
b268e9ee74 Улучшение парсинга html 2019-11-13 19:41:20 +07:00
Book Pauk
e97774435b Merge tag '0.7.7a' into develop
0.7.7a
2019-11-08 17:21:17 +07:00
Book Pauk
93586bc5bb Merge branch 'release/0.7.7a' 2019-11-08 17:21:03 +07:00
Book Pauk
fe23089714 Небольшие поправки верстки 2019-11-08 17:20:31 +07:00
Book Pauk
e743986f38 Merge tag '0.7.7' into develop
0.7.7
2019-11-06 20:18:02 +07:00
Book Pauk
a6c9b700ed Merge branch 'release/0.7.7' 2019-11-06 20:17:42 +07:00
Book Pauk
afa3fcb524 Merge branch 'feature/lss' into develop 2019-11-06 20:17:25 +07:00
Book Pauk
b9aeb648d6 Версия 0.7.7 2019-11-06 20:16:56 +07:00
Book Pauk
5f5df1e5b7 Добавлены жесты для тачскрина 2019-11-06 20:02:21 +07:00
Book Pauk
ad885679e4 Merge branch 'develop' into feature/lss 2019-11-03 18:06:19 +07:00
Book Pauk
e002bebfbe Merge tag '0.7.6c' into develop
0.7.6c
2019-11-03 18:05:52 +07:00
Book Pauk
a8a41e2b3d Merge branch 'release/0.7.6c' 2019-11-03 18:05:42 +07:00
Book Pauk
31940caa84 Поправки логирования 2019-11-03 18:05:02 +07:00
Book Pauk
880334054e Merge tag '0.7.6b' into develop
0.7.6b
2019-11-03 17:56:44 +07:00
Book Pauk
5f03ad5597 Merge branch 'release/0.7.6b' 2019-11-03 17:56:33 +07:00
Book Pauk
1efa3f055d Merge branch 'feature/lss' into develop 2019-11-03 17:56:10 +07:00
Book Pauk
8ccf11278b Поправки логирования 2019-11-03 17:55:43 +07:00
Book Pauk
8a9e7ab4c3 Merge branch 'develop' into feature/lss 2019-11-03 17:06:04 +07:00
Book Pauk
c0fa7c0c51 Merge tag '0.7.6a' into develop
0.7.6a
2019-11-03 17:04:50 +07:00
Book Pauk
022dfd4709 Merge branch 'release/0.7.6a' 2019-11-03 17:04:39 +07:00
Book Pauk
71e08aacc3 Поправки багов 2019-11-03 17:03:58 +07:00
Book Pauk
337eca87f2 Merge tag '0.7.6' into develop
0.7.6
2019-10-30 17:37:19 +07:00
Book Pauk
074aceff8f Merge branch 'release/0.7.6' 2019-10-30 17:37:10 +07:00
Book Pauk
cdc6cf229a Версия 0.7.6 2019-10-30 17:36:46 +07:00
Book Pauk
1f33513dc9 Merge branch 'feature/lss' into develop 2019-10-30 17:35:18 +07:00
Book Pauk
b095b91ff2 Поправки багов 2019-10-30 17:33:13 +07:00
Book Pauk
454a62dbb9 Работа над MegaStorage 2019-10-29 21:03:56 +07:00
Book Pauk
5f7cc12157 Поправка путей 2019-10-29 19:07:34 +07:00
Book Pauk
97ef1ee201 Рефакторинг 2019-10-29 19:05:21 +07:00
Book Pauk
a318568b72 Рефакторинг конфига 2019-10-29 18:58:40 +07:00
Book Pauk
5bb9949440 Изменение путей к модулям 2019-10-29 14:45:55 +07:00
Book Pauk
c33e91d5d0 Рефакторинг, преобразование классов в синглтоны 2019-10-29 14:37:05 +07:00
Book Pauk
ca65ef3cb7 ReaderWorker теперь синглтон 2019-10-28 23:33:10 +07:00
Book Pauk
9ebdbc81d0 Рефакторинг 2019-10-28 23:30:02 +07:00
Book Pauk
b64985349e Рефакторинг 2019-10-28 22:49:02 +07:00
Book Pauk
625fd9d1a4 Поправки 2019-10-28 21:17:52 +07:00
Book Pauk
eac5fdcec0 Рефакторинг, поправки 2019-10-28 21:14:50 +07:00
Book Pauk
970b4d5d97 Мелкий рефакторинг 2019-10-28 20:52:04 +07:00
Book Pauk
f741bc818d Добавлен метод readFiles 2019-10-28 20:31:06 +07:00
Book Pauk
5f04c24187 Убрал adm-zip 2019-10-28 16:04:09 +07:00
Book Pauk
a382bef336 Замена AdmZip на ZipStreamer 2019-10-28 16:02:44 +07:00
Book Pauk
4ddf28f344 Добавлен метод unpack 2019-10-28 15:55:26 +07:00
Book Pauk
0dc650305a Поправки потенциальных багов, мелкий рефакторинг 2019-10-27 19:36:50 +07:00
Book Pauk
697093d1c9 Поправка бага 2019-10-27 19:27:26 +07:00
Book Pauk
622f7a4479 Исправление бага 2019-10-27 19:24:18 +07:00
Book Pauk
c4b607804b Заготовка LibSharedStorage 2019-10-27 19:19:20 +07:00
Book Pauk
864f008679 Работа над MegaStorage 2019-10-27 19:18:37 +07:00
Book Pauk
25f309bcb0 Добавлены модули node-stream-zip, zip-stream 2019-10-27 17:57:21 +07:00
Book Pauk
1354361ad9 Убрал дебаг 2019-10-27 17:55:54 +07:00
Book Pauk
8136c7b072 Начало работы над LibSharedStorage 2019-10-25 21:18:09 +07:00
Book Pauk
c9243e7249 Поправка конфигов 2019-10-25 21:17:50 +07:00
Book Pauk
1a487da3d9 Небольшой рефакторинг 2019-10-25 21:16:22 +07:00
Book Pauk
b52395751c Поправил размер кнопок 2019-10-25 13:37:20 +07:00
Book Pauk
cfa6cc9a83 Merge tag '0.7.5a' into develop
0.7.5a
2019-10-23 23:58:03 +07:00
Book Pauk
f203384b00 Merge branch 'release/0.7.5a' 2019-10-23 23:57:51 +07:00
Book Pauk
9ac3be455c Поправки багов 2019-10-23 23:53:37 +07:00
Book Pauk
20b74a9dcd Небольшие поправки 2019-10-23 20:42:14 +07:00
Book Pauk
3b848a5a86 Исправления конфига nginx 2019-10-23 20:07:38 +07:00
Book Pauk
a9b5e865a5 Merge tag '0.7.5b' into develop
0.7.5b
2019-10-22 18:24:20 +07:00
47 changed files with 1049 additions and 308 deletions

View File

@@ -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">

View File

@@ -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>

View File

@@ -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,13 +22,16 @@
<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'">
data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram" <div ref="yaShare2" class="ya-share2"
data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется." data-services="collections,vkontakte,facebook,odnoklassniki,twitter,telegram"
data-title="Omni Reader - браузерная онлайн-читалка" data-description="Чтение fb2-книг онлайн. Загрузка любой страницы интернета одним кликом, синхронизация между устройствами, удобное управление, регистрация не требуется."
data-url="https://omnireader.ru"> data-title="Omni Reader - браузерная онлайн-читалка"
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>
@@ -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 {

View File

@@ -220,14 +220,19 @@ export default @Component({
bookPos: function(newValue) { bookPos: function(newValue) {
if (newValue !== undefined && this.activePage == 'TextPage') { if (newValue !== undefined && this.activePage == 'TextPage') {
const textPage = this.$refs.page; const textPage = this.$refs.page;
if (textPage.bookPos != newValue) { if (textPage.bookPos != newValue) {
textPage.bookPos = newValue; textPage.bookPos = newValue;
} }
this.debouncedSetRecentBook(newValue);
if (!this.scrollingActive)
this.debouncedSetRecentBook(newValue);
else
this.scrollingSetRecentBook(newValue);
} }
}, },
routeParamPos: function(newValue) { routeParamPos: function(newValue) {
if (newValue !== undefined && newValue != this.bookPos) { if (!this.paramPosIgnore && newValue !== undefined && newValue != this.bookPos) {
this.bookPos = newValue; this.bookPos = newValue;
} }
}, },
@@ -291,10 +296,6 @@ class Reader extends Vue {
this.lastActivePage = false; this.lastActivePage = false;
this.debouncedUpdateRoute = _.debounce(() => {
this.updateRoute();
}, 1000);
this.debouncedSetRecentBook = _.debounce(async(newValue) => { this.debouncedSetRecentBook = _.debounce(async(newValue) => {
const recent = this.mostRecentBook(); const recent = this.mostRecentBook();
if (recent && (recent.bookPos != newValue || recent.bookPosSeen !== this.bookPosSeen)) { if (recent && (recent.bookPos != newValue || recent.bookPosSeen !== this.bookPosSeen)) {
@@ -302,8 +303,17 @@ class Reader extends Vue {
if (this.actionCur < 0 || (this.actionCur >= 0 && this.actionList[this.actionCur] != newValue)) if (this.actionCur < 0 || (this.actionCur >= 0 && this.actionList[this.actionCur] != newValue))
this.addAction(newValue); this.addAction(newValue);
this.paramPosIgnore = true;
this.updateRoute();
await this.$nextTick();
this.paramPosIgnore = false;
} }
}, 500); }, 500, {'maxWait':5000});
this.scrollingSetRecentBook = _.debounce((newValue) => {
this.debouncedSetRecentBook(newValue);
}, 15000, {'maxWait':20000});
document.addEventListener('fullscreenchange', () => { document.addEventListener('fullscreenchange', () => {
this.fullScreenActive = (document.fullscreenElement !== null); this.fullScreenActive = (document.fullscreenElement !== null);
@@ -343,6 +353,8 @@ class Reader extends Vue {
await this.showWhatsNew(); await this.showWhatsNew();
await this.showMigration(); await this.showMigration();
this.updateRoute();
})(); })();
} }
@@ -507,7 +519,6 @@ class Reader extends Vue {
if (event.bookPosSeen !== undefined) if (event.bookPosSeen !== undefined)
this.bookPosSeen = event.bookPosSeen; this.bookPosSeen = event.bookPosSeen;
this.bookPos = event.bookPos; this.bookPos = event.bookPos;
this.debouncedUpdateRoute();
} }
async bookManagerEvent(eventName, value) { async bookManagerEvent(eventName, value) {
@@ -654,6 +665,10 @@ class Reader extends Vue {
page.stopTextScrolling(); page.stopTextScrolling();
} }
} }
if (!this.scrollingActive) {
this.scrollingSetRecentBook.flush();
}
} }
stopSearch() { stopSearch() {

View File

@@ -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 {

View File

@@ -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,9 +510,10 @@ class ServerStorage extends Vue {
if (result.state == 'reject') { if (result.state == 'reject') {
await this.loadRecent(false, false); const res = await this.loadRecent(false, false);
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`); if (res)
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
if (!recurse && itemKey) { if (!recurse && itemKey) {
this.savingRecent = false; this.savingRecent = false;
this.saveRecent(itemKey, true); this.saveRecent(itemKey, true);

View File

@@ -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']));
this.repDoing = true; if (hc) {
this.debouncedStartClickRepeat(x, y); if (hc != 'Menu') {
this.repDoing = true;
this.debouncedStartClickRepeat(x, y);
} else {
this.startTouch = {x, y};
}
} }
} }
} }
onTouchEnd() { onTouchMove(event) {
if (this.startTouch) {
event.preventDefault();
}
}
onTouchEnd(event) {
if (!this.$isMobileDevice) 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,27 +1141,35 @@ class TextPage extends Vue {
} }
} }
switch (action) { return action;
case 'Down' ://Down }
this.doDown();
break; handleClick(pointX, pointY, exclude) {
case 'Up' ://Up const action = this.getClickAction(pointX, pointY);
this.doUp();
break; if (!exclude || !exclude.has(action)) {
case 'PgDown' ://PgDown switch (action) {
this.doPageDown(); case 'Down' ://Down
break; this.doDown();
case 'PgUp' ://PgUp break;
this.doPageUp(); case 'Up' ://Up
break; this.doUp();
case 'Menu' : break;
this.doToolBarToggle(); case 'PgDown' ://PgDown
break; this.doPageDown();
default : break;
// Nothing case 'PgUp' ://PgUp
this.doPageUp();
break;
case 'Menu' :
this.doToolBarToggle();
break;
default :
// Nothing
}
} }
return (action && action != 'Menu'); return action;
} }
} }

View File

@@ -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) => {

View File

@@ -1,4 +1,32 @@
export const versionHistory = [ export const versionHistory = [
{
showUntil: '2019-11-10',
header: '0.7.7 (2019-11-06)',
content:
`
<ul>
<li>добавлены следующие жесты для тачскрина (только при включенной опции "управление кликом"):</li>
<ul>
<li style="list-style-type: square">от центра вверх: на весь экран</li>
<li style="list-style-type: square">от центра вниз: плавный скроллинг</li>
<li style="list-style-type: square">от центра вправо: увеличить скорость скроллинга</li>
<li style="list-style-type: square">от центра влево: уменьшить скорость скроллинга</li>
</ul>
</ul>
`
},
{
showUntil: '2019-10-29',
header: '0.7.6 (2019-10-30)',
content:
`
<ul>
<li>исправления багов</li>
</ul>
`
},
{ {
showUntil: '2019-10-21', showUntil: '2019-10-21',
header: '0.7.5 (2019-10-22)', header: '0.7.5 (2019-10-22)',

View File

@@ -15,7 +15,7 @@ server {
gzip_types *; gzip_types *;
location /api { location /api {
proxy_pass http://localhost:44081; proxy_pass http://127.0.0.1:44081;
} }
location / { location / {
@@ -44,7 +44,7 @@ server {
gzip_types *; gzip_types *;
location /api { location /api {
proxy_pass http://localhost:44081; proxy_pass http://127.0.0.1:44081;
} }
location /tmp { location /tmp {

250
package-lock.json generated
View File

@@ -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"
}
}
}
} }
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "Liberama", "name": "Liberama",
"version": "0.7.5", "version": "0.7.7",
"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"
} }
} }

View File

@@ -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Мб

View File

@@ -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
};

View File

@@ -1,6 +1,5 @@
const base = require('./base'); const base = require('./base');
module.exports = Object.assign({}, base, { module.exports = Object.assign({}, base, {
branch: 'development', branch: 'development',
} });
);

View File

@@ -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;
}
return instance;
}
async init() {
if (this.inited)
throw new Error('already inited');
this.branch = 'production';
try {
await fs.access(branchFilename);
this.branch = (await fs.readFile(branchFilename, 'utf8')).trim();
} catch (err) {
//
}
process.env.NODE_ENV = this.branch;
this.branchConfigFile = __dirname + `/${this.branch}.js`;
await fs.access(this.branchConfigFile);
this._config = require(this.branchConfigFile);
this._userConfigFile = `${this._config.dataDir}/config.json`;
this.inited = true;
}
get config() {
if (!this.inited)
throw new Error('not inited');
return _.cloneDeep(this._config);
}
set config(value) {
Object.assign(this._config, value);
}
get userConfigFile() {
return this._userConfigFile;
}
set userConfigFile(value) {
if (value)
this._userConfigFile = value;
}
async load() {
if (!this.inited)
throw new Error('not inited');
if (!await fs.pathExists(this.userConfigFile)) {
await this.save();
return;
}
const data = await fs.readFile(this.userConfigFile, 'utf8');
this.config = JSON.parse(data);
}
async save() {
if (!this.inited)
throw new Error('not inited');
const dataToSave = _.pick(this._config, propsToSave);
await fs.writeFile(this.userConfigFile, JSON.stringify(dataToSave, null, 4));
}
} }
process.env.NODE_ENV = branch; module.exports = ConfigManager;
const confFilename = __dirname + `/${branch}.js`;
fs.accessSync(confFilename);
const config = require(confFilename);
//fs.ensureDirSync(config.dataDir);
module.exports = config;

View File

@@ -5,21 +5,21 @@ const execDir = path.dirname(process.execPath);
const dataDir = `${execDir}/data`; const dataDir = `${execDir}/data`;
module.exports = Object.assign({}, base, { module.exports = Object.assign({}, base, {
branch: 'production', branch: 'production',
dataDir: dataDir, dataDir: dataDir,
tempDir: `${dataDir}/tmp`, tempDir: `${dataDir}/tmp`,
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: [
{ {
serverName: '1', serverName: '1',
mode: 'normal', //'none', 'normal', 'site', 'reader', 'omnireader' mode: 'normal', //'none', 'normal', 'site', 'reader', 'omnireader'
ip: '0.0.0.0', ip: '0.0.0.0',
port: '44080', port: '44080',
}, },
], ],
} });
);

View File

@@ -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 {

View File

@@ -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;
} }

View File

@@ -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
View 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;

View File

@@ -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);
} }

View 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;

View File

@@ -0,0 +1,4 @@
class LibSharedStorage {
}
module.exports = LibSharedStorage;

View File

@@ -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(/&nbsp;/g, ' ')));
} }
formatFb2(fb2) { formatFb2(fb2) {

View File

@@ -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 {
@@ -64,6 +64,7 @@ class ConvertHtml extends ConvertBase {
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;
@@ -171,7 +172,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;
@@ -257,8 +257,9 @@ 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 a = []; let a = [];
const onTextNode = (text) => { const onTextNode = (text) => {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 = [

View File

@@ -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.periodicCleanCache(3*3600*1000);//1 раз в 3 часа this.connManager = new ConnManager();
this.storagePool = this.connManager.pool.readerStorage;
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;

View File

@@ -1,35 +1,40 @@
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) {
this.config = Object.assign({}, config); if (!instance) {
this.config = Object.assign({}, config);
this.config.tempDownloadDir = `${config.tempDir}/download`;
fs.ensureDirSync(this.config.tempDownloadDir); this.config.tempDownloadDir = `${config.tempDir}/download`;
fs.ensureDirSync(this.config.tempDownloadDir);
this.config.tempPublicDir = `${config.publicDir}/tmp`; this.config.tempPublicDir = `${config.publicDir}/tmp`;
fs.ensureDirSync(this.config.tempPublicDir); fs.ensureDirSync(this.config.tempPublicDir);
this.down = new FileDownloader(); this.workerState = new WorkerState();
this.decomp = new FileDecompressor(); this.down = new FileDownloader();
this.bookConverter = new BookConverter(this.config); this.decomp = new FileDecompressor();
this.bookConverter = new BookConverter(this.config);
if (!singleCleanExecute) {
this.periodicCleanDir(this.config.tempPublicDir, this.config.maxTempPublicDirSize, 60*60*1000);//1 раз в час this.periodicCleanDir(this.config.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);

View File

@@ -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() {
this.states = {}; if (!instance) {
setTimeout(this.cleanStates.bind(this), cleanInterval*1000); this.states = {};
this.cleanStates();
instance = this;
}
return instance;
} }
generateWorkerId() { generateWorkerId() {
@@ -51,6 +59,4 @@ class WorkerState {
} }
} }
const workerState = new WorkerState(); module.exports = WorkerState;
module.exports = workerState;

View 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;

View File

@@ -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,
};

View File

@@ -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,

View File

@@ -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;

View File

@@ -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');

View File

@@ -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);