Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90c3faadcf | ||
|
|
e594206759 | ||
|
|
80b21371a4 | ||
|
|
af575a87a2 | ||
|
|
a6aa8d8e95 | ||
|
|
aa436feae7 | ||
|
|
abce84999f | ||
|
|
74bb3f2362 | ||
|
|
abcbbc86bc | ||
|
|
d859202fb1 | ||
|
|
d71c235ebc | ||
|
|
49583d3407 | ||
|
|
a7f71562b4 | ||
|
|
f93269ac8b | ||
|
|
79f85f362a | ||
|
|
bf9ae0b9e1 | ||
|
|
678562525f | ||
|
|
abb3baf94b | ||
|
|
59b4f48897 | ||
|
|
dca59e02dd | ||
|
|
9bc92a7346 | ||
|
|
53264da6bd | ||
|
|
53e328ac2d | ||
|
|
59c2d73c05 |
48
CHANGELOG.md
Normal file
48
CHANGELOG.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
1.3.3 / 2022-11-28
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Исправление выявленных недочетов
|
||||||
|
|
||||||
|
1.3.2 / 2022-11-27
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Изменения механизма ограничения доступа по паролю:
|
||||||
|
- появилась возможность выхода из сессии
|
||||||
|
- в конфиг добавлена настройка таймаута для автозавершения сессии
|
||||||
|
- Добавлено отображение количества книг в серии в разделе "Авторы"
|
||||||
|
|
||||||
|
1.3.1 / 2022-11-25
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Улучшена кроссплатформенность приложения
|
||||||
|
|
||||||
|
1.3.0 / 2022-11-24
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Добавлен OPDS-сервер для inpx-коллекции
|
||||||
|
- Произведена небольшая оптимизация поисковой БД
|
||||||
|
- Добавлен релиз для macos, без тестирования
|
||||||
|
|
||||||
|
1.2.4 / 2022-11-14
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Добавлена возможность посмотреть обложку в увеличении
|
||||||
|
- Исправление выявленных недочетов
|
||||||
|
|
||||||
|
1.2.3 / 2022-11-12
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Добавлено диалоговое окно "Информация о книге"
|
||||||
|
- Небольшие изменения интерфейса, добавлена кнопка "Клонировать поиск"
|
||||||
|
|
||||||
|
1.1.4 / 2022-11-03
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Исправлен баг "Не качает книги #1"
|
||||||
|
|
||||||
|
1.1.2 / 2022-10-31
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Добавлены разделы "Серии" и "Книги"
|
||||||
|
- Расширена форма поиска: добавлен поиск по датам поступления и оценкам
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ OPDS-сервер доступен по адресу [http://127.0.0.1:12380/opd
|
|||||||
- фильтр авторов и книг при создании поисковой БД для создания своей коллекции "на лету"
|
- фильтр авторов и книг при создании поисковой БД для создания своей коллекции "на лету"
|
||||||
- подхват изменений .inpx-файла (периодическая проверка), автоматическое пересоздание поисковой БД
|
- подхват изменений .inpx-файла (периодическая проверка), автоматическое пересоздание поисковой БД
|
||||||
- мощная оптимизация, хорошая скорость поиска
|
- мощная оптимизация, хорошая скорость поиска
|
||||||
- релизы под Linux и Windows
|
- релизы под Linux, MacOS и Windows
|
||||||
|
|
||||||
<a id="usage" />
|
<a id="usage" />
|
||||||
|
|
||||||
@@ -79,8 +79,14 @@ Options:
|
|||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
// пароль для ограничения доступа к веб-интерфейсу сервера
|
// пароль для ограничения доступа к веб-интерфейсу сервера
|
||||||
|
// пустое значение - доступ без ограничений
|
||||||
"accessPassword": "",
|
"accessPassword": "",
|
||||||
|
|
||||||
|
// таймаут автозавершения сессии доступа к веб-интерфейсу (если задан accessPassword),
|
||||||
|
// при неактивности в течение указанного времени (в минутах), пароль будет запрошен заново
|
||||||
|
// 0 - отключить таймаут, время доступа по паролю не ограничено
|
||||||
|
"accessTimeout": 0,
|
||||||
|
|
||||||
// содержимое кнопки-ссылки "(читать)", если не задано - кнопка "(читать)" не показывается
|
// содержимое кнопки-ссылки "(читать)", если не задано - кнопка "(читать)" не показывается
|
||||||
// пример: "https://omnireader.ru/#/reader?url=${DOWNLOAD_LINK}"
|
// пример: "https://omnireader.ru/#/reader?url=${DOWNLOAD_LINK}"
|
||||||
// на место ${DOWNLOAD_LINK} будет подставлена ссылка на скачивание файла книги
|
// на место ${DOWNLOAD_LINK} будет подставлена ссылка на скачивание файла книги
|
||||||
|
|||||||
@@ -60,10 +60,21 @@ const componentOptions = {
|
|||||||
settings() {
|
settings() {
|
||||||
this.loadSettings();
|
this.loadSettings();
|
||||||
},
|
},
|
||||||
|
modelValue(newValue) {
|
||||||
|
this.accessGranted = newValue;
|
||||||
|
},
|
||||||
|
accessGranted(newValue) {
|
||||||
|
this.$emit('update:modelValue', newValue);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
class Api {
|
class Api {
|
||||||
_options = componentOptions;
|
_options = componentOptions;
|
||||||
|
_props = {
|
||||||
|
modelValue: Boolean,
|
||||||
|
};
|
||||||
|
accessGranted = false;
|
||||||
|
|
||||||
busyDialogVisible = false;
|
busyDialogVisible = false;
|
||||||
mainMessage = '';
|
mainMessage = '';
|
||||||
jobMessage = '';
|
jobMessage = '';
|
||||||
@@ -98,10 +109,6 @@ class Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get config() {
|
|
||||||
return this.$store.state.config;
|
|
||||||
}
|
|
||||||
|
|
||||||
get settings() {
|
get settings() {
|
||||||
return this.$store.state.settings;
|
return this.$store.state.settings;
|
||||||
}
|
}
|
||||||
@@ -123,7 +130,13 @@ class Api {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result && result.value) {
|
if (result && result.value) {
|
||||||
const accessToken = utils.toHex(cryptoUtils.sha256(result.value));
|
//получим свежую соль
|
||||||
|
const response = await wsc.message(await wsc.send({}), 10);
|
||||||
|
let salt = '';
|
||||||
|
if (response && response.error == 'need_access_token' && response.salt)
|
||||||
|
salt = response.salt;
|
||||||
|
|
||||||
|
const accessToken = utils.toHex(cryptoUtils.sha256(result.value + salt));
|
||||||
this.commit('setSettings', {accessToken});
|
this.commit('setSettings', {accessToken});
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -192,10 +205,13 @@ class Api {
|
|||||||
const response = await wsc.message(await wsc.send(params), timeoutSecs);
|
const response = await wsc.message(await wsc.send(params), timeoutSecs);
|
||||||
|
|
||||||
if (response && response.error == 'need_access_token') {
|
if (response && response.error == 'need_access_token') {
|
||||||
|
this.accessGranted = false;
|
||||||
await this.showPasswordDialog();
|
await this.showPasswordDialog();
|
||||||
} else if (response && response.error == 'server_busy') {
|
} else if (response && response.error == 'server_busy') {
|
||||||
|
this.accessGranted = true;
|
||||||
await this.showBusyDialog();
|
await this.showBusyDialog();
|
||||||
} else {
|
} else {
|
||||||
|
this.accessGranted = true;
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
}
|
}
|
||||||
@@ -223,6 +239,10 @@ class Api {
|
|||||||
return await this.request({action: 'get-author-book-list', authorId});
|
return await this.request({action: 'get-author-book-list', authorId});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAuthorSeriesList(authorId) {
|
||||||
|
return await this.request({action: 'get-author-series-list', authorId});
|
||||||
|
}
|
||||||
|
|
||||||
async getSeriesBookList(series) {
|
async getSeriesBookList(series) {
|
||||||
return await this.request({action: 'get-series-book-list', series});
|
return await this.request({action: 'get-series-book-list', series});
|
||||||
}
|
}
|
||||||
@@ -242,6 +262,12 @@ class Api {
|
|||||||
async getConfig() {
|
async getConfig() {
|
||||||
return await this.request({action: 'get-config'});
|
return await this.request({action: 'get-config'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
await this.request({action: 'logout'});
|
||||||
|
this.accessGranted = false;
|
||||||
|
await this.request({action: 'test'});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default vueComponent(Api);
|
export default vueComponent(Api);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fit row">
|
<div class="fit row">
|
||||||
<Api ref="api" />
|
<Api ref="api" v-model="accessGranted" />
|
||||||
<Notify ref="notify" />
|
<Notify ref="notify" />
|
||||||
<StdDialog ref="stdDialog" />
|
<StdDialog ref="stdDialog" />
|
||||||
|
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-if="accessGranted" v-slot="{ Component }">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component :is="Component" class="col" />
|
<component :is="Component" class="col" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
@@ -37,6 +37,7 @@ const componentOptions = {
|
|||||||
};
|
};
|
||||||
class App {
|
class App {
|
||||||
_options = componentOptions;
|
_options = componentOptions;
|
||||||
|
accessGranted = false;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<div class="q-ml-sm text-bold" style="color: #555">
|
<div class="q-ml-sm text-bold" style="color: #555">
|
||||||
{{ getBookCount(item) }}
|
{{ getBookCount(item) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="item.bookLoading" class="book-row row items-center">
|
<div v-if="item.bookLoading" class="book-row row items-center">
|
||||||
@@ -54,6 +54,10 @@
|
|||||||
<div class="clickable2 q-ml-xs q-py-sm text-bold" @click="selectSeries(book.series)">
|
<div class="clickable2 q-ml-xs q-py-sm text-bold" @click="selectSeries(book.series)">
|
||||||
Серия: {{ book.series }}
|
Серия: {{ book.series }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="q-ml-sm text-bold" style="color: #555">
|
||||||
|
{{ getSeriesBookCount(item, book) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isExpandedSeries(book) && book.seriesBooks">
|
<div v-if="isExpandedSeries(book) && book.seriesBooks">
|
||||||
@@ -184,6 +188,22 @@ class AuthorList extends BaseList {
|
|||||||
return `(${result})`;
|
return `(${result})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSeriesBookCount(item, book) {
|
||||||
|
let result = '';
|
||||||
|
if (!this.showCounts || book.type != 'series')
|
||||||
|
return result;
|
||||||
|
|
||||||
|
let count = book.seriesBooks.length;
|
||||||
|
result = `${count}`;
|
||||||
|
if (item.seriesLoaded) {
|
||||||
|
const rec = item.seriesLoaded[book.series];
|
||||||
|
const totalCount = (this.showDeleted ? rec.bookCount + rec.bookDelCount : rec.bookCount);
|
||||||
|
result += `/${totalCount}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `(${result})`;
|
||||||
|
}
|
||||||
|
|
||||||
async expandAuthor(item) {
|
async expandAuthor(item) {
|
||||||
this.$emit('listEvent', {action: 'ignoreScroll'});
|
this.$emit('listEvent', {action: 'ignoreScroll'});
|
||||||
|
|
||||||
@@ -209,6 +229,19 @@ class AuthorList extends BaseList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAuthorSeries(item) {
|
||||||
|
if (item.seriesLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const series = await this.loadAuthorSeries(item.key);
|
||||||
|
const loaded = {};
|
||||||
|
for (const s of series) {
|
||||||
|
loaded[s.series] = {bookCount: s.bookCount, bookDelCount: s.bookDelCount};
|
||||||
|
}
|
||||||
|
|
||||||
|
item.seriesLoaded = loaded;
|
||||||
|
}
|
||||||
|
|
||||||
async getAuthorBooks(item) {
|
async getAuthorBooks(item) {
|
||||||
if (item.books) {
|
if (item.books) {
|
||||||
if (item.count > this.maxItemCount) {
|
if (item.count > this.maxItemCount) {
|
||||||
@@ -310,6 +343,7 @@ class AuthorList extends BaseList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.booksLoaded = books;
|
item.booksLoaded = books;
|
||||||
|
this.getAuthorSeries(item);//no await
|
||||||
this.showMore(item);
|
this.showMore(item);
|
||||||
|
|
||||||
await this.$nextTick();
|
await this.$nextTick();
|
||||||
@@ -342,6 +376,7 @@ class AuthorList extends BaseList {
|
|||||||
name: rec.author.replace(/,/g, ', '),
|
name: rec.author.replace(/,/g, ', '),
|
||||||
count,
|
count,
|
||||||
booksLoaded: false,
|
booksLoaded: false,
|
||||||
|
seriesLoaded: false,
|
||||||
books: false,
|
books: false,
|
||||||
bookLoading: false,
|
bookLoading: false,
|
||||||
showMore: false,
|
showMore: false,
|
||||||
|
|||||||
@@ -253,7 +253,30 @@ export default class BaseList {
|
|||||||
result = await this.api.getAuthorBookList(authorId);
|
result = await this.api.getAuthorBookList(authorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (result.books ? JSON.parse(result.books) : []);
|
return result.books;
|
||||||
|
} catch (e) {
|
||||||
|
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadAuthorSeries(authorId) {
|
||||||
|
try {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
if (this.abCacheEnabled) {
|
||||||
|
const key = `author-${authorId}-series-${this.list.inpxHash}`;
|
||||||
|
const data = await authorBooksStorage.getData(key);
|
||||||
|
if (data) {
|
||||||
|
result = JSON.parse(data);
|
||||||
|
} else {
|
||||||
|
result = await this.api.getAuthorSeriesList(authorId);
|
||||||
|
await authorBooksStorage.setData(key, JSON.stringify(result));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = await this.api.getAuthorSeriesList(authorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.series;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
||||||
}
|
}
|
||||||
@@ -276,7 +299,7 @@ export default class BaseList {
|
|||||||
result = await this.api.getSeriesBookList(series);
|
result = await this.api.getSeriesBookList(series);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (result.books ? JSON.parse(result.books) : []);
|
return result.books;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,14 @@
|
|||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</DivBtn>
|
</DivBtn>
|
||||||
|
|
||||||
|
<DivBtn v-if="!config.freeAccess" class="q-ml-sm text-white bg-secondary" :size="30" :icon-size="24" :imt="1" icon="la la-sign-out-alt" round @click.stop.prevent="logout">
|
||||||
|
<template #tooltip>
|
||||||
|
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%" max-width="400px">
|
||||||
|
Выход
|
||||||
|
</q-tooltip>
|
||||||
|
</template>
|
||||||
|
</DivBtn>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mx-md q-mb-xs items-center">
|
<div class="row q-mx-md q-mb-xs items-center">
|
||||||
<DivBtn
|
<DivBtn
|
||||||
@@ -427,6 +435,8 @@ class Search {
|
|||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
(async() => {
|
(async() => {
|
||||||
|
await this.api.updateConfig();
|
||||||
|
|
||||||
//для встраивания в liberama
|
//для встраивания в liberama
|
||||||
window.addEventListener('message', (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
if (!_.isObject(event.data) || event.data.from != 'ExternalLibs')
|
if (!_.isObject(event.data) || event.data.from != 'ExternalLibs')
|
||||||
@@ -979,6 +989,10 @@ class Search {
|
|||||||
cloneSearch() {
|
cloneSearch() {
|
||||||
window.open(window.location.href, '_blank');
|
window.open(window.location.href, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
await this.api.logout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default vueComponent(Search);
|
export default vueComponent(Search);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ const abStore = localForage.createInstance({
|
|||||||
name: 'authorBooksStorage'
|
name: 'authorBooksStorage'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const storageVersion = '1';
|
||||||
|
|
||||||
class AuthorBooksStorage {
|
class AuthorBooksStorage {
|
||||||
constructor() {
|
constructor() {
|
||||||
}
|
}
|
||||||
@@ -17,6 +19,8 @@ class AuthorBooksStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setData(key, data) {
|
async setData(key, data) {
|
||||||
|
key += storageVersion;
|
||||||
|
|
||||||
if (typeof data !== 'string')
|
if (typeof data !== 'string')
|
||||||
throw new Error('AuthorBooksStorage: data must be a string');
|
throw new Error('AuthorBooksStorage: data must be a string');
|
||||||
|
|
||||||
@@ -25,6 +29,8 @@ class AuthorBooksStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getData(key) {
|
async getData(key) {
|
||||||
|
key += storageVersion;
|
||||||
|
|
||||||
const item = await abStore.getItem(key);
|
const item = await abStore.getItem(key);
|
||||||
|
|
||||||
//обновим addTime
|
//обновим addTime
|
||||||
@@ -34,9 +40,9 @@ class AuthorBooksStorage {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeData(key) {
|
async _removeData(fullKey) {
|
||||||
await abStore.removeItem(key);
|
await abStore.removeItem(fullKey);
|
||||||
await abStore.removeItem(`addTime-${key}`);
|
await abStore.removeItem(`addTime-${fullKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanStorage() {
|
async cleanStorage() {
|
||||||
@@ -62,7 +68,7 @@ class AuthorBooksStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (size > maxDataSize && toDel) {
|
if (size > maxDataSize && toDel) {
|
||||||
await this.removeData(toDel);
|
await this._removeData(toDel);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "inpx-web",
|
"name": "inpx-web",
|
||||||
"version": "1.3.0",
|
"version": "1.3.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "inpx-web",
|
"name": "inpx-web",
|
||||||
"version": "1.3.0",
|
"version": "1.3.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"jembadb": "^5.1.3",
|
"jembadb": "^5.1.4",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
@@ -5072,9 +5072,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jembadb": {
|
"node_modules/jembadb": {
|
||||||
"version": "5.1.3",
|
"version": "5.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-5.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-5.1.4.tgz",
|
||||||
"integrity": "sha512-HGl9d3/fcNNahOqEsb3ocpXRWEfmDwV2zgWvKXERwlsxOHqoEId2fHXPkjv97qRywEyE/n9U8WimIWsP2Evf4w==",
|
"integrity": "sha512-VGg800ZhEXDdWCJ1y2ZcgRqdBWo2g+/55LusBh3r9S3ruKJeRkwVv4nGqHXbtdW60QJNsSwUyQbzcku8zhzvXw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.16.0"
|
"node": ">=16.16.0"
|
||||||
}
|
}
|
||||||
@@ -12594,9 +12594,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"jembadb": {
|
"jembadb": {
|
||||||
"version": "5.1.3",
|
"version": "5.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-5.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/jembadb/-/jembadb-5.1.4.tgz",
|
||||||
"integrity": "sha512-HGl9d3/fcNNahOqEsb3ocpXRWEfmDwV2zgWvKXERwlsxOHqoEId2fHXPkjv97qRywEyE/n9U8WimIWsP2Evf4w=="
|
"integrity": "sha512-VGg800ZhEXDdWCJ1y2ZcgRqdBWo2g+/55LusBh3r9S3ruKJeRkwVv4nGqHXbtdW60QJNsSwUyQbzcku8zhzvXw=="
|
||||||
},
|
},
|
||||||
"jest-worker": {
|
"jest-worker": {
|
||||||
"version": "27.5.1",
|
"version": "27.5.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "inpx-web",
|
"name": "inpx-web",
|
||||||
"version": "1.3.0",
|
"version": "1.3.3",
|
||||||
"author": "Book Pauk <bookpauk@gmail.com>",
|
"author": "Book Pauk <bookpauk@gmail.com>",
|
||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"repository": "bookpauk/inpx-web",
|
"repository": "bookpauk/inpx-web",
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
"jembadb": "^5.1.3",
|
"jembadb": "^5.1.4",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ module.exports = {
|
|||||||
execDir,
|
execDir,
|
||||||
|
|
||||||
accessPassword: '',
|
accessPassword: '',
|
||||||
|
accessTimeout: 0,
|
||||||
bookReadLink: '',
|
bookReadLink: '',
|
||||||
loggingEnabled: true,
|
loggingEnabled: true,
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const branchFilename = __dirname + '/application_env';
|
|||||||
|
|
||||||
const propsToSave = [
|
const propsToSave = [
|
||||||
'accessPassword',
|
'accessPassword',
|
||||||
|
'accessTimeout',
|
||||||
'bookReadLink',
|
'bookReadLink',
|
||||||
'loggingEnabled',
|
'loggingEnabled',
|
||||||
'dbCacheSize',
|
'dbCacheSize',
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ const cleanPeriod = 1*60*1000;//1 минута
|
|||||||
const closeSocketOnIdle = 5*60*1000;//5 минут
|
const closeSocketOnIdle = 5*60*1000;//5 минут
|
||||||
|
|
||||||
class WebSocketController {
|
class WebSocketController {
|
||||||
constructor(wss, config) {
|
constructor(wss, webAccess, config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.isDevelopment = (config.branch == 'development');
|
this.isDevelopment = (config.branch == 'development');
|
||||||
this.accessToken = '';
|
|
||||||
if (config.accessPassword)
|
this.webAccess = webAccess;
|
||||||
this.accessToken = utils.getBufHash(config.accessPassword, 'sha256', 'hex');
|
|
||||||
|
|
||||||
this.workerState = new WorkerState();
|
this.workerState = new WorkerState();
|
||||||
this.webWorker = new WebWorker(config);
|
this.webWorker = new WebWorker(config);
|
||||||
@@ -32,19 +31,25 @@ class WebSocketController {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
this.periodicClean();//no await
|
||||||
}
|
}
|
||||||
|
|
||||||
periodicClean() {
|
async periodicClean() {
|
||||||
try {
|
while (1) {//eslint-disable-line no-constant-condition
|
||||||
const now = Date.now();
|
try {
|
||||||
this.wss.clients.forEach((ws) => {
|
const now = Date.now();
|
||||||
if (!ws.lastActivity || now - ws.lastActivity > closeSocketOnIdle - 50) {
|
|
||||||
ws.terminate();
|
//почистим ws-клиентов
|
||||||
}
|
this.wss.clients.forEach((ws) => {
|
||||||
});
|
if (!ws.lastActivity || now - ws.lastActivity > closeSocketOnIdle - 50) {
|
||||||
} finally {
|
ws.terminate();
|
||||||
setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
}
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
log(LM_ERR, `WebSocketController.periodicClean error: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.sleep(cleanPeriod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,14 +67,20 @@ class WebSocketController {
|
|||||||
//pong for WebSocketConnection
|
//pong for WebSocketConnection
|
||||||
this.send({_rok: 1}, req, ws);
|
this.send({_rok: 1}, req, ws);
|
||||||
|
|
||||||
if (this.accessToken && req.accessToken !== this.accessToken) {
|
//access
|
||||||
await utils.sleep(1000);
|
if (!await this.webAccess.hasAccess(req.accessToken)) {
|
||||||
throw new Error('need_access_token');
|
await utils.sleep(500);
|
||||||
|
const salt = this.webAccess.newToken();
|
||||||
|
this.send({error: 'need_access_token', salt}, req, ws);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//api
|
||||||
switch (req.action) {
|
switch (req.action) {
|
||||||
case 'test':
|
case 'test':
|
||||||
await this.test(req, ws); break;
|
await this.test(req, ws); break;
|
||||||
|
case 'logout':
|
||||||
|
await this.logout(req, ws); break;
|
||||||
case 'get-config':
|
case 'get-config':
|
||||||
await this.getConfig(req, ws); break;
|
await this.getConfig(req, ws); break;
|
||||||
case 'get-worker-state':
|
case 'get-worker-state':
|
||||||
@@ -78,6 +89,8 @@ class WebSocketController {
|
|||||||
await this.search(req, ws); break;
|
await this.search(req, ws); break;
|
||||||
case 'get-author-book-list':
|
case 'get-author-book-list':
|
||||||
await this.getAuthorBookList(req, ws); break;
|
await this.getAuthorBookList(req, ws); break;
|
||||||
|
case 'get-author-series-list':
|
||||||
|
await this.getAuthorSeriesList(req, ws); break;
|
||||||
case 'get-series-book-list':
|
case 'get-series-book-list':
|
||||||
await this.getSeriesBookList(req, ws); break;
|
await this.getSeriesBookList(req, ws); break;
|
||||||
case 'get-genre-tree':
|
case 'get-genre-tree':
|
||||||
@@ -120,9 +133,15 @@ class WebSocketController {
|
|||||||
this.send({message: `${this.config.name} project is awesome`}, req, ws);
|
this.send({message: `${this.config.name} project is awesome`}, req, ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async logout(req, ws) {
|
||||||
|
await this.webAccess.deleteAccess(req.accessToken);
|
||||||
|
this.send({success: true}, req, ws);
|
||||||
|
}
|
||||||
|
|
||||||
async getConfig(req, ws) {
|
async getConfig(req, ws) {
|
||||||
const config = _.pick(this.config, this.config.webConfigParams);
|
const config = _.pick(this.config, this.config.webConfigParams);
|
||||||
config.dbConfig = await this.webWorker.dbConfig();
|
config.dbConfig = await this.webWorker.dbConfig();
|
||||||
|
config.freeAccess = this.webAccess.freeAccess;
|
||||||
|
|
||||||
this.send(config, req, ws);
|
this.send(config, req, ws);
|
||||||
}
|
}
|
||||||
@@ -152,6 +171,12 @@ class WebSocketController {
|
|||||||
this.send(result, req, ws);
|
this.send(result, req, ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAuthorSeriesList(req, ws) {
|
||||||
|
const result = await this.webWorker.getAuthorSeriesList(req.authorId);
|
||||||
|
|
||||||
|
this.send(result, req, ws);
|
||||||
|
}
|
||||||
|
|
||||||
async getSeriesBookList(req, ws) {
|
async getSeriesBookList(req, ws) {
|
||||||
const result = await this.webWorker.getSeriesBookList(req.series);
|
const result = await this.webWorker.getSeriesBookList(req.series);
|
||||||
|
|
||||||
|
|||||||
@@ -599,7 +599,7 @@ class DbSearcher {
|
|||||||
throw new Error('DbSearcher closed');
|
throw new Error('DbSearcher closed');
|
||||||
|
|
||||||
if (!authorId && !author)
|
if (!authorId && !author)
|
||||||
return {author: '', books: ''};
|
return {author: '', books: []};
|
||||||
|
|
||||||
this.searchFlag++;
|
this.searchFlag++;
|
||||||
|
|
||||||
@@ -625,14 +625,60 @@ class DbSearcher {
|
|||||||
const rows = await this.restoreBooks('author', [authorId]);
|
const rows = await this.restoreBooks('author', [authorId]);
|
||||||
|
|
||||||
let authorName = '';
|
let authorName = '';
|
||||||
let books = '';
|
let books = [];
|
||||||
|
|
||||||
if (rows.length) {
|
if (rows.length) {
|
||||||
authorName = rows[0].name;
|
authorName = rows[0].name;
|
||||||
books = rows[0].books;
|
books = rows[0].books;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {author: authorName, books: (books && books.length ? JSON.stringify(books) : '')};
|
return {author: authorName, books};
|
||||||
|
} finally {
|
||||||
|
this.searchFlag--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAuthorSeriesList(authorId) {
|
||||||
|
if (this.closed)
|
||||||
|
throw new Error('DbSearcher closed');
|
||||||
|
|
||||||
|
if (!authorId)
|
||||||
|
return {author: '', series: []};
|
||||||
|
|
||||||
|
this.searchFlag++;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const db = this.db;
|
||||||
|
|
||||||
|
//выборка книг автора по authorId
|
||||||
|
const bookList = await this.getAuthorBookList(authorId);
|
||||||
|
const books = bookList.books;
|
||||||
|
const seriesSet = new Set();
|
||||||
|
for (const book of books) {
|
||||||
|
if (book.series)
|
||||||
|
seriesSet.add(book.series.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
let series = [];
|
||||||
|
if (seriesSet.size) {
|
||||||
|
//выборка серий по названиям
|
||||||
|
series = await db.select({
|
||||||
|
table: 'series',
|
||||||
|
map: `(r) => ({id: r.id, series: r.name, bookCount: r.bookCount, bookDelCount: r.bookDelCount})`,
|
||||||
|
where: `
|
||||||
|
const seriesArr = ${db.esc(Array.from(seriesSet))};
|
||||||
|
const ids = new Set();
|
||||||
|
for (const value of seriesArr) {
|
||||||
|
for (const id of @dirtyIndexLR('value', value, value))
|
||||||
|
ids.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {author: bookList.author, series};
|
||||||
} finally {
|
} finally {
|
||||||
this.searchFlag--;
|
this.searchFlag--;
|
||||||
}
|
}
|
||||||
@@ -643,7 +689,7 @@ class DbSearcher {
|
|||||||
throw new Error('DbSearcher closed');
|
throw new Error('DbSearcher closed');
|
||||||
|
|
||||||
if (!series)
|
if (!series)
|
||||||
return {books: ''};
|
return {books: []};
|
||||||
|
|
||||||
this.searchFlag++;
|
this.searchFlag++;
|
||||||
|
|
||||||
@@ -659,7 +705,7 @@ class DbSearcher {
|
|||||||
where: `return Array.from(@dirtyIndexLR('value', ${db.esc(series)}, ${db.esc(series)}))`
|
where: `return Array.from(@dirtyIndexLR('value', ${db.esc(series)}, ${db.esc(series)}))`
|
||||||
});
|
});
|
||||||
|
|
||||||
let books;
|
let books = [];
|
||||||
if (rows.length && rows[0].rawResult.length) {
|
if (rows.length && rows[0].rawResult.length) {
|
||||||
//выборка книг серии
|
//выборка книг серии
|
||||||
const bookRows = await this.restoreBooks('series', [rows[0].rawResult[0]])
|
const bookRows = await this.restoreBooks('series', [rows[0].rawResult[0]])
|
||||||
@@ -668,7 +714,7 @@ class DbSearcher {
|
|||||||
books = bookRows[0].books;
|
books = bookRows[0].books;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {books: (books && books.length ? JSON.stringify(books) : '')};
|
return {books};
|
||||||
} finally {
|
} finally {
|
||||||
this.searchFlag--;
|
this.searchFlag--;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ class RemoteLib {
|
|||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
this.wsc = new WebSocketConnection(config.remoteLib.url, 10, 30, {rejectUnauthorized: false});
|
this.wsc = new WebSocketConnection(config.remoteLib.url, 10, 30, {rejectUnauthorized: false});
|
||||||
if (config.remoteLib.accessPassword)
|
|
||||||
this.accessToken = utils.getBufHash(config.remoteLib.accessPassword, 'sha256', 'hex');
|
|
||||||
|
|
||||||
this.remoteHost = config.remoteLib.url.replace(/^ws:\/\//, 'http://').replace(/^wss:\/\//, 'https://');
|
this.remoteHost = config.remoteLib.url.replace(/^ws:\/\//, 'http://').replace(/^wss:\/\//, 'https://');
|
||||||
|
|
||||||
@@ -31,7 +29,7 @@ class RemoteLib {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
async wsRequest(query) {
|
async wsRequest(query, recurse = false) {
|
||||||
if (this.accessToken)
|
if (this.accessToken)
|
||||||
query.accessToken = this.accessToken;
|
query.accessToken = this.accessToken;
|
||||||
|
|
||||||
@@ -40,6 +38,11 @@ class RemoteLib {
|
|||||||
120
|
120
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!recurse && response && response.error == 'need_access_token' && this.config.remoteLib.accessPassword) {
|
||||||
|
this.accessToken = utils.getBufHash(this.config.remoteLib.accessPassword + response.salt, 'sha256', 'hex');
|
||||||
|
return await this.wsRequest(query, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (response.error)
|
if (response.error)
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
|
|
||||||
|
|||||||
154
server/core/WebAccess.js
Normal file
154
server/core/WebAccess.js
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
const { JembaDbThread } = require('jembadb');
|
||||||
|
const utils = require('../core/utils');
|
||||||
|
const log = new (require('../core/AppLogger'))().log;//singleton
|
||||||
|
const asyncExit = new (require('./AsyncExit'))();
|
||||||
|
|
||||||
|
const cleanPeriod = 1*60*1000;//1 минута
|
||||||
|
const cleanUnusedTokenTimeout = 5*60*1000;//5 минут
|
||||||
|
|
||||||
|
class WebAccess {
|
||||||
|
constructor(config) {
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
this.freeAccess = (config.accessPassword === '');
|
||||||
|
this.accessTimeout = config.accessTimeout*60*1000;
|
||||||
|
this.accessMap = new Map();
|
||||||
|
|
||||||
|
asyncExit.add(this.closeDb.bind(this));
|
||||||
|
|
||||||
|
setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const config = this.config;
|
||||||
|
const dbPath = `${config.dataDir}/web-access`;
|
||||||
|
const db = new JembaDbThread();//в отдельном потоке
|
||||||
|
await db.lock({
|
||||||
|
dbPath,
|
||||||
|
create: true,
|
||||||
|
softLock: true,
|
||||||
|
|
||||||
|
tableDefaults: {
|
||||||
|
cacheSize: config.dbCacheSize,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
//открываем таблицы
|
||||||
|
await db.openAll();
|
||||||
|
} catch(e) {
|
||||||
|
if (
|
||||||
|
e.message.indexOf('corrupted') >= 0
|
||||||
|
|| e.message.indexOf('Unexpected token') >= 0
|
||||||
|
|| e.message.indexOf('invalid stored block lengths') >= 0
|
||||||
|
) {
|
||||||
|
log(LM_ERR, `DB ${dbPath} corrupted`);
|
||||||
|
log(`Open "${dbPath}" with auto repair`);
|
||||||
|
await db.openAll({autoRepair: true});
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.create({table: 'access', quietIfExists: true});
|
||||||
|
//проверим, нужно ли обнулить таблицу access
|
||||||
|
const pass = utils.getBufHash(this.config.accessPassword, 'sha256', 'hex');
|
||||||
|
await db.create({table: 'config', quietIfExists: true});
|
||||||
|
let rows = await db.select({table: 'config', where: `@@id('pass')`});
|
||||||
|
|
||||||
|
if (!rows.length || rows[0].value !== pass) {
|
||||||
|
//пароль сменился в конфиге, обнуляем токены
|
||||||
|
await db.truncate({table: 'access'});
|
||||||
|
await db.insert({table: 'config', replace: true, rows: [{id: 'pass', value: pass}]});
|
||||||
|
}
|
||||||
|
|
||||||
|
//загрузим токены сессий
|
||||||
|
rows = await db.select({table: 'access'});
|
||||||
|
for (const row of rows)
|
||||||
|
this.accessMap.set(row.id, row.value);
|
||||||
|
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
async closeDb() {
|
||||||
|
if (this.db) {
|
||||||
|
await this.db.unlock();
|
||||||
|
this.db = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async periodicClean() {
|
||||||
|
while (1) {//eslint-disable-line no-constant-condition
|
||||||
|
try {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
//почистим accessMap
|
||||||
|
if (!this.freeAccess) {
|
||||||
|
for (const [accessToken, accessRec] of this.accessMap) {
|
||||||
|
if ( !(accessRec.used > 0 || now - accessRec.time < cleanUnusedTokenTimeout)
|
||||||
|
|| !(this.accessTimeout === 0 || now - accessRec.time < this.accessTimeout)
|
||||||
|
) {
|
||||||
|
await this.deleteAccess(accessToken);
|
||||||
|
} else if (!accessRec.saved) {
|
||||||
|
await this.saveAccess(accessToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
log(LM_ERR, `WebAccess.periodicClean error: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.sleep(cleanPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async hasAccess(accessToken) {
|
||||||
|
if (this.freeAccess)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const accessRec = this.accessMap.get(accessToken);
|
||||||
|
if (accessRec) {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (this.accessTimeout === 0 || now - accessRec.time < this.accessTimeout) {
|
||||||
|
accessRec.used++;
|
||||||
|
accessRec.time = now;
|
||||||
|
accessRec.saved = false;
|
||||||
|
if (accessRec.used === 1)
|
||||||
|
await this.saveAccess(accessToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteAccess(accessToken) {
|
||||||
|
await this.db.delete({table: 'access', where: `@@id(${this.db.esc(accessToken)})`});
|
||||||
|
this.accessMap.delete(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveAccess(accessToken) {
|
||||||
|
const value = this.accessMap.get(accessToken);
|
||||||
|
if (!value || value.saved)
|
||||||
|
return;
|
||||||
|
|
||||||
|
value.saved = true;
|
||||||
|
await this.db.insert({
|
||||||
|
table: 'access',
|
||||||
|
replace: true,
|
||||||
|
rows: [{id: accessToken, value}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
newToken() {
|
||||||
|
const salt = utils.randomHexString(32);
|
||||||
|
const accessToken = utils.getBufHash(this.config.accessPassword + salt, 'sha256', 'hex');
|
||||||
|
this.accessMap.set(accessToken, {time: Date.now(), used: 0});
|
||||||
|
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WebAccess;
|
||||||
@@ -11,7 +11,7 @@ const DbSearcher = require('./DbSearcher');
|
|||||||
const InpxHashCreator = require('./InpxHashCreator');
|
const InpxHashCreator = require('./InpxHashCreator');
|
||||||
const RemoteLib = require('./RemoteLib');//singleton
|
const RemoteLib = require('./RemoteLib');//singleton
|
||||||
|
|
||||||
const ayncExit = new (require('./AsyncExit'))();
|
const asyncExit = new (require('./AsyncExit'))();
|
||||||
const log = new (require('./AppLogger'))().log;//singleton
|
const log = new (require('./AppLogger'))().log;//singleton
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const genreTree = require('./genres');
|
const genreTree = require('./genres');
|
||||||
@@ -53,7 +53,7 @@ class WebWorker {
|
|||||||
this.db = null;
|
this.db = null;
|
||||||
this.dbSearcher = null;
|
this.dbSearcher = null;
|
||||||
|
|
||||||
ayncExit.add(this.closeDb.bind(this));
|
asyncExit.add(this.closeDb.bind(this));
|
||||||
|
|
||||||
this.loadOrCreateDb();//no await
|
this.loadOrCreateDb();//no await
|
||||||
this.periodicLogServerStats();//no await
|
this.periodicLogServerStats();//no await
|
||||||
@@ -221,7 +221,7 @@ class WebWorker {
|
|||||||
this.logServerStats();
|
this.logServerStats();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(LM_FATAL, e.message);
|
log(LM_FATAL, e.message);
|
||||||
ayncExit.exit(1);
|
asyncExit.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +279,12 @@ class WebWorker {
|
|||||||
return await this.dbSearcher.getAuthorBookList(authorId, author);
|
return await this.dbSearcher.getAuthorBookList(authorId, author);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAuthorSeriesList(authorId) {
|
||||||
|
this.checkMyState();
|
||||||
|
|
||||||
|
return await this.dbSearcher.getAuthorSeriesList(authorId);
|
||||||
|
}
|
||||||
|
|
||||||
async getSeriesBookList(series) {
|
async getSeriesBookList(series) {
|
||||||
this.checkMyState();
|
this.checkMyState();
|
||||||
|
|
||||||
@@ -628,7 +634,7 @@ class WebWorker {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(LM_FATAL, e.message);
|
log(LM_FATAL, e.message);
|
||||||
ayncExit.exit(1);
|
asyncExit.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class AuthorPage extends BasePage {
|
|||||||
const bookList = await this.webWorker.getSeriesBookList(query.series);
|
const bookList = await this.webWorker.getSeriesBookList(query.series);
|
||||||
|
|
||||||
if (bookList.books) {
|
if (bookList.books) {
|
||||||
let books = JSON.parse(bookList.books);
|
let books = bookList.books;
|
||||||
const booksAll = this.filterBooks(books, {del: 0});
|
const booksAll = this.filterBooks(books, {del: 0});
|
||||||
const filtered = (query.all ? booksAll : this.filterBooks(books, query));
|
const filtered = (query.all ? booksAll : this.filterBooks(books, query));
|
||||||
const sorted = this.sortSeriesBooks(filtered);
|
const sorted = this.sortSeriesBooks(filtered);
|
||||||
@@ -122,7 +122,7 @@ class AuthorPage extends BasePage {
|
|||||||
const bookList = await this.webWorker.getAuthorBookList(0, query.author.substring(1));
|
const bookList = await this.webWorker.getAuthorBookList(0, query.author.substring(1));
|
||||||
|
|
||||||
if (bookList.books) {
|
if (bookList.books) {
|
||||||
let books = JSON.parse(bookList.books);
|
let books = bookList.books;
|
||||||
books = this.sortBooks(this.filterBooks(books, query));
|
books = this.sortBooks(this.filterBooks(books, query));
|
||||||
|
|
||||||
for (const b of books) {
|
for (const b of books) {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class SeriesPage extends BasePage {
|
|||||||
const bookList = await this.webWorker.getSeriesBookList(query.series.substring(1));
|
const bookList = await this.webWorker.getSeriesBookList(query.series.substring(1));
|
||||||
|
|
||||||
if (bookList.books) {
|
if (bookList.books) {
|
||||||
let books = JSON.parse(bookList.books);
|
let books = bookList.books;
|
||||||
const booksAll = this.filterBooks(books, {del: 0});
|
const booksAll = this.filterBooks(books, {del: 0});
|
||||||
const filtered = (query.all ? booksAll : this.filterBooks(books, query));
|
const filtered = (query.all ? booksAll : this.filterBooks(books, query));
|
||||||
const sorted = this.sortSeriesBooks(filtered);
|
const sorted = this.sortSeriesBooks(filtered);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function processLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function versionText(config) {
|
function versionText(config) {
|
||||||
return `${config.name} v${config.version}, Node.js ${process.version}`;
|
return `${config.name} v${config.version}, Node.js ${process.version}, ${process.platform}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findFiles(callback, dir, recursive = true) {
|
async function findFiles(callback, dir, recursive = true) {
|
||||||
|
|||||||
@@ -158,8 +158,11 @@ async function main() {
|
|||||||
opds(app, config);
|
opds(app, config);
|
||||||
initStatic(app, config);
|
initStatic(app, config);
|
||||||
|
|
||||||
|
const webAccess = new (require('./core/WebAccess'))(config);
|
||||||
|
await webAccess.init();
|
||||||
|
|
||||||
const { WebSocketController } = require('./controllers');
|
const { WebSocketController } = require('./controllers');
|
||||||
new WebSocketController(wss, config);
|
new WebSocketController(wss, webAccess, config);
|
||||||
|
|
||||||
if (devModule) {
|
if (devModule) {
|
||||||
devModule.logErrors(app);
|
devModule.logErrors(app);
|
||||||
|
|||||||
Reference in New Issue
Block a user