18 Commits
1.5.1 ... 1.5.3

Author SHA1 Message Date
Book Pauk
636e34bdc1 Merge branch 'release/1.5.3' 2023-03-02 18:15:13 +07:00
Book Pauk
2c687c7af9 CHANGELOG 2023-03-02 18:14:54 +07:00
Book Pauk
27fc46c410 Версия 1.5.3 2023-03-02 18:14:38 +07:00
Book Pauk
a7cd019a97 CHANGELOG 2023-03-02 18:12:13 +07:00
Book Pauk
12304c13a1 Добавлена полоска уведомления о выходе новой версии (отключается в настройках веб-интерфейса). Проверка настраивается параметром checkReleaseLink в конфиге сревера (#15) 2023-03-02 18:10:15 +07:00
Book Pauk
d87e2ce632 CHANGELOG 2023-03-02 15:59:58 +07:00
Book Pauk
a2423fb704 opds: улучшено скачивание для не-fb2 форматов файлов (djvu, pdf и пр.) 2023-03-02 15:50:26 +07:00
Book Pauk
192b92cab8 Поправка бага (не показывалось имя автора для не-fb2 файлов) 2023-03-02 14:58:12 +07:00
Book Pauk
386a937239 CHANGELOG 2023-03-02 14:34:45 +07:00
Book Pauk
06300e30b4 Добавлен костыль - неверная кодировка от koreader при поиске в opds 2023-03-02 14:33:01 +07:00
Book Pauk
371d5646ae Merge tag '1.5.2' into develop
1.5.2
2023-02-05 17:50:59 +07:00
Book Pauk
0d52a9fd52 Merge branch 'release/1.5.2' 2023-02-05 17:50:52 +07:00
Book Pauk
08287deaa4 Версия 1.5.2 2023-02-05 17:50:26 +07:00
Book Pauk
c43c5520a4 Улучшена обработка ошибок 2023-02-05 17:48:02 +07:00
Book Pauk
4e2b7886a9 Мелкий рефакторинг 2023-02-05 17:43:24 +07:00
Book Pauk
05744e8472 CHANGELOG 2023-02-05 17:35:32 +07:00
Book Pauk
9126973378 Исправление проблемы чтения каталога opds для koreader 2023-02-05 17:35:24 +07:00
Book Pauk
018e1069d5 Merge tag '1.5.1' into develop
1.5.1
2023-01-28 21:21:18 +07:00
17 changed files with 141 additions and 33 deletions

View File

@@ -1,3 +1,14 @@
1.5.3 / 2023-03-02
- OPDS: исправление проблемы поиска для koreader
- OPDS: улучшено скачивание для не-fb2 форматов файлов (djvu, pdf и пр.)
- Добавлена полоска уведомления о выходе новой версии (отключается в настройках веб-интерфейса).
Проверка новой версии настраивается параметром checkReleaseLink в конфиге сервера (#15)
1.5.2 / 2023-02-05
- Исправление проблемы чтения каталога opds для koreader
1.5.1 / 2023-01-28 1.5.1 / 2023-01-28
------------------ ------------------

View File

@@ -174,6 +174,13 @@ Options:
"root": "/opds" "root": "/opds"
}, },
// страница для скачивания свежего релиза
"latestReleaseLink": "https://github.com/bookpauk/inpx-web/releases/latest",
// api для проверки новой версии,
// пустая строка - отключить проверку выхода новых версий
"checkReleaseLink": "https://api.github.com/repos/bookpauk/inpx-web/releases/latest",
// настройки по умолчанию для веб-интерфейса // настройки по умолчанию для веб-интерфейса
// устанавливаются при первой загрузке страницы в браузере // устанавливаются при первой загрузке страницы в браузере
// дальнейшие изменения настроек с помощью веб-интерфейса уже сохраняются в самом браузере // дальнейшие изменения настроек с помощью веб-интерфейса уже сохраняются в самом браузере
@@ -188,7 +195,8 @@ Options:
"showDeleted": false, // показывать удаленные "showDeleted": false, // показывать удаленные
"abCacheEnabled": true, // кешировать запросы "abCacheEnabled": true, // кешировать запросы
"langDefault": "", // язык по умолчанию (например "ru,en") "langDefault": "", // язык по умолчанию (например "ru,en")
"showJson": false // показывать JSON (в расширенном поиске) "showJson": false, // показывать JSON (в расширенном поиске)
"showNewReleaseAvailable": true // уведомлять о выходе новой версии
} }
} }
``` ```

View File

@@ -3,6 +3,19 @@
<div ref="scroller" class="col fit column no-wrap" style="overflow: auto; position: relative" @scroll="onScroll"> <div ref="scroller" class="col fit column no-wrap" style="overflow: auto; position: relative" @scroll="onScroll">
<!-- Tool Panel begin --> <!-- Tool Panel begin -->
<div ref="toolPanel" class="tool-panel column bg-cyan-2" style="position: sticky; top: 0; z-index: 10;"> <div ref="toolPanel" class="tool-panel column bg-cyan-2" style="position: sticky; top: 0; z-index: 10;">
<!-- Обновление -->
<div v-show="showNewReleaseAvailable && newReleaseAvailable" class="row q-py-sm bg-green-4 items-center">
<div class="q-ml-sm" style="font-size: 120%">
Доступна новая версия <b>{{ config.name }} v{{ config.latestVersion }}</b>
</div>
<DivBtn class="q-ml-sm q-px-sm bg-white" :size="20" @click.stop.prevent="openReleasePage">
Скачать
</DivBtn>
<DivBtn class="q-ml-sm q-px-sm bg-white" :size="20" @click.stop.prevent="settingsDialogVisible = true">
Отключить уведомление
</DivBtn>
</div>
<!-- 1 --> <!-- 1 -->
<div class="row"> <div class="row">
<!-- 1-1 --> <!-- 1-1 -->
@@ -504,6 +517,7 @@ class Search {
limit = 20; limit = 20;
extendedParams = false; extendedParams = false;
showJson = false; showJson = false;
showNewReleaseAvailable = true;
//stuff //stuff
prevList = {}; prevList = {};
@@ -555,6 +569,7 @@ class Search {
mounted() { mounted() {
(async() => { (async() => {
//для срабатывания watch.config
await this.api.updateConfig(); await this.api.updateConfig();
//устанавливаем uiDefaults от сервера, если это необходимо //устанавливаем uiDefaults от сервера, если это необходимо
@@ -604,6 +619,7 @@ class Search {
this.abCacheEnabled = settings.abCacheEnabled; this.abCacheEnabled = settings.abCacheEnabled;
this.langDefault = settings.langDefault; this.langDefault = settings.langDefault;
this.showJson = settings.showJson; this.showJson = settings.showJson;
this.showNewReleaseAvailable = settings.showNewReleaseAvailable;
} }
recvMessage(d) { recvMessage(d) {
@@ -631,6 +647,10 @@ class Search {
return this.$store.state.config; return this.$store.state.config;
} }
get newReleaseAvailable() {
return (this.config.latestVersion && this.config.version != this.config.latestVersion);
}
get recStruct() { get recStruct() {
if (this.config.dbConfig && this.config.dbConfig.inpxInfo.recStruct) if (this.config.dbConfig && this.config.dbConfig.inpxInfo.recStruct)
return this.config.dbConfig.inpxInfo.recStruct; return this.config.dbConfig.inpxInfo.recStruct;
@@ -728,14 +748,19 @@ class Search {
} }
openReleasePage() { openReleasePage() {
window.open('https://github.com/bookpauk/inpx-web/releases', '_blank'); if (this.config.latestReleaseLink)
window.open(this.config.latestReleaseLink, '_blank');
} }
makeProjectName() { makeProjectName() {
const collection = this.config.dbConfig.inpxInfo.collection.split('\n'); const collection = this.config.dbConfig.inpxInfo.collection.split('\n');
this.collection = collection[0].trim(); this.collection = collection[0].trim();
this.projectName = `${this.config.name} v${this.config.webAppVersion}`; let projectName = `${this.config.name} v${this.config.webAppVersion}`;
if (this.newReleaseAvailable)
projectName += `, доступно обновление: v${this.config.latestVersion}`;
this.projectName = projectName;
this.makeTitle(); this.makeTitle();
} }

View File

@@ -19,6 +19,7 @@
/> />
</div> </div>
<q-checkbox v-show="config.latestVersion" v-model="showNewReleaseAvailable" size="36px" label="Уведомлять о выходе новой версии" />
<q-checkbox v-model="downloadAsZip" size="36px" label="Скачивать книги в виде zip-архива" /> <q-checkbox v-model="downloadAsZip" size="36px" label="Скачивать книги в виде zip-архива" />
<q-checkbox v-model="showCounts" size="36px" label="Показывать количество" /> <q-checkbox v-model="showCounts" size="36px" label="Показывать количество" />
<q-checkbox v-model="showRates" size="36px" label="Показывать оценки" /> <q-checkbox v-model="showRates" size="36px" label="Показывать оценки" />
@@ -85,6 +86,9 @@ const componentOptions = {
abCacheEnabled(newValue) { abCacheEnabled(newValue) {
this.commit('setSettings', {'abCacheEnabled': newValue}); this.commit('setSettings', {'abCacheEnabled': newValue});
}, },
showNewReleaseAvailable(newValue) {
this.commit('setSettings', {'showNewReleaseAvailable': newValue});
},
} }
}; };
class SettingsDialog { class SettingsDialog {
@@ -105,6 +109,7 @@ class SettingsDialog {
showDates = true; showDates = true;
showDeleted = false; showDeleted = false;
abCacheEnabled = true; abCacheEnabled = true;
showNewReleaseAvailable = true;
limitOptions = [ limitOptions = [
{label: '10', value: 10}, {label: '10', value: 10},
@@ -125,6 +130,10 @@ class SettingsDialog {
mounted() { mounted() {
} }
get config() {
return this.$store.state.config;
}
get settings() { get settings() {
return this.$store.state.settings; return this.$store.state.settings;
} }
@@ -142,6 +151,7 @@ class SettingsDialog {
this.showDates = settings.showDates; this.showDates = settings.showDates;
this.showDeleted = settings.showDeleted; this.showDeleted = settings.showDeleted;
this.abCacheEnabled = settings.abCacheEnabled; this.abCacheEnabled = settings.abCacheEnabled;
this.showNewReleaseAvailable = settings.showNewReleaseAvailable;
} }
okClick() { okClick() {

View File

@@ -89,8 +89,10 @@ export default vueComponent(DivBtn);
} }
.button-pressed { .button-pressed {
margin-left: 2px; margin-left: 1px;
margin-top: 2px; margin-top: 1px;
margin-right: -1px;
margin-bottom: -1px;
} }
.clickable { .clickable {

View File

@@ -21,6 +21,7 @@ const state = {
abCacheEnabled: true, abCacheEnabled: true,
langDefault: '', langDefault: '',
showJson: false, showJson: false,
showNewReleaseAvailable: true,
}, },
}; };

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "inpx-web", "name": "inpx-web",
"version": "1.5.1", "version": "1.5.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "inpx-web", "name": "inpx-web",
"version": "1.5.1", "version": "1.5.3",
"hasInstallScript": true, "hasInstallScript": true,
"license": "CC0-1.0", "license": "CC0-1.0",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "inpx-web", "name": "inpx-web",
"version": "1.5.1", "version": "1.5.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",

View File

@@ -6,6 +6,7 @@ const execDir = path.resolve(__dirname, '..');
module.exports = { module.exports = {
branch: 'unknown', branch: 'unknown',
version: pckg.version, version: pckg.version,
latestVersion: '',
name: pckg.name, name: pckg.name,
execDir, execDir,
@@ -19,7 +20,7 @@ module.exports = {
loggingEnabled: true, loggingEnabled: true,
//поправить в случае, если были критические изменения в DbCreator или InpxParser //поправить в случае, если были критические изменения в DbCreator или InpxParser
//иначе будет рассинхронизация между сервером и клиентом на уровне БД //иначе будет рассинхронизация по кешу между сервером и клиентом на уровне БД
dbVersion: '11', dbVersion: '11',
dbCacheSize: 5, dbCacheSize: 5,
@@ -33,7 +34,7 @@ module.exports = {
lowMemoryMode: false, lowMemoryMode: false,
fullOptimization: false, fullOptimization: false,
webConfigParams: ['name', 'version', 'branch', 'bookReadLink', 'dbVersion', 'extendedSearch', 'uiDefaults'], webConfigParams: ['name', 'version', 'latestVersion', 'branch', 'bookReadLink', 'dbVersion', 'extendedSearch', 'latestReleaseLink', 'uiDefaults'],
allowRemoteLib: false, allowRemoteLib: false,
remoteLib: false, remoteLib: false,
@@ -57,6 +58,10 @@ module.exports = {
password: '', password: '',
root: '/opds', root: '/opds',
}, },
latestReleaseLink: 'https://github.com/bookpauk/inpx-web/releases/latest',
checkReleaseLink: 'https://api.github.com/repos/bookpauk/inpx-web/releases/latest',
uiDefaults: { uiDefaults: {
limit: 20, limit: 20,
downloadAsZip: false, downloadAsZip: false,
@@ -69,6 +74,7 @@ module.exports = {
abCacheEnabled: true, abCacheEnabled: true,
langDefault: '', langDefault: '',
showJson: false, showJson: false,
showNewReleaseAvailable: true,
}, },
}; };

View File

@@ -25,6 +25,8 @@ const propsToSave = [
'remoteLib', 'remoteLib',
'server', 'server',
'opds', 'opds',
'latestReleaseLink',
'checkReleaseLink',
'uiDefaults', 'uiDefaults',
]; ];

View File

@@ -10,6 +10,7 @@ const DbCreator = require('./DbCreator');
const DbSearcher = require('./DbSearcher'); const DbSearcher = require('./DbSearcher');
const InpxHashCreator = require('./InpxHashCreator'); const InpxHashCreator = require('./InpxHashCreator');
const RemoteLib = require('./RemoteLib');//singleton const RemoteLib = require('./RemoteLib');//singleton
const FileDownloader = require('./FileDownloader');
const asyncExit = new (require('./AsyncExit'))(); const asyncExit = new (require('./AsyncExit'))();
const log = new (require('./AppLogger'))().log;//singleton const log = new (require('./AppLogger'))().log;//singleton
@@ -28,7 +29,8 @@ const stateToText = {
[ssDbCreating]: 'Создание поисковой базы', [ssDbCreating]: 'Создание поисковой базы',
}; };
const cleanDirPeriod = 60*60*1000;//каждый час const cleanDirInterval = 60*60*1000;//каждый час
const checkReleaseInterval = 2*60*60*1000;//каждые 2 часа
//singleton //singleton
let instance = null; let instance = null;
@@ -67,6 +69,7 @@ class WebWorker {
this.periodicCleanDir(dirConfig);//no await this.periodicCleanDir(dirConfig);//no await
this.periodicCheckInpx();//no await this.periodicCheckInpx();//no await
this.periodicCheckNewRelease();//no await
instance = this; instance = this;
} }
@@ -638,7 +641,7 @@ class WebWorker {
let lastCleanDirTime = 0; let lastCleanDirTime = 0;
while (1) {// eslint-disable-line no-constant-condition while (1) {// eslint-disable-line no-constant-condition
//чистка папок //чистка папок
if (Date.now() - lastCleanDirTime >= cleanDirPeriod) { if (Date.now() - lastCleanDirTime >= cleanDirInterval) {
for (const config of dirConfig) { for (const config of dirConfig) {
try { try {
await this.cleanDir(config); await this.cleanDir(config);
@@ -690,6 +693,27 @@ class WebWorker {
await utils.sleep(inpxCheckInterval*60*1000); await utils.sleep(inpxCheckInterval*60*1000);
} }
} }
async periodicCheckNewRelease() {
const checkReleaseLink = this.config.checkReleaseLink;
if (!checkReleaseLink)
return;
const down = new FileDownloader(1024*1024);
while (1) {// eslint-disable-line no-constant-condition
try {
let release = await down.load(checkReleaseLink);
release = JSON.parse(release.toString());
if (release.tag_name)
this.config.latestVersion = release.tag_name;
} catch(e) {
log(LM_ERR, `periodicCheckNewRelease: ${e.message}`);
}
await utils.sleep(checkReleaseInterval);
}
}
} }
module.exports = WebWorker; module.exports = WebWorker;

View File

@@ -24,13 +24,18 @@ class BasePage {
this.showDeleted = false; this.showDeleted = false;
} }
escape(s) {
//костыль для koreader, не понимает hex-экранирование вида &#x27;
return he.escape(s).replace(/&#x27;/g, '&#39;').replace(/&#x60;/g, '&#96;');
}
makeEntry(entry = {}) { makeEntry(entry = {}) {
if (!entry.id) if (!entry.id)
throw new Error('makeEntry: no id'); throw new Error('makeEntry: no id');
if (!entry.title) if (!entry.title)
throw new Error('makeEntry: no title'); throw new Error('makeEntry: no title');
entry.title = he.escape(entry.title); entry.title = this.escape(entry.title);
const result = { const result = {
updated: (new Date()).toISOString().substring(0, 19) + 'Z', updated: (new Date()).toISOString().substring(0, 19) + 'Z',
@@ -48,7 +53,7 @@ class BasePage {
} }
makeLink(attrs) { makeLink(attrs) {
attrs.href = he.escape(attrs.href); attrs.href = this.escape(attrs.href);
return {'*ATTRS': attrs}; return {'*ATTRS': attrs};
} }

View File

@@ -1,6 +1,5 @@
const path = require('path'); const path = require('path');
const _ = require('lodash'); const _ = require('lodash');
const he = require('he');
const dayjs = require('dayjs'); const dayjs = require('dayjs');
const BasePage = require('./BasePage'); const BasePage = require('./BasePage');
@@ -131,15 +130,15 @@ class BookPage extends BasePage {
//format //format
const ext = bookInfo.book.ext; const ext = bookInfo.book.ext;
let fileFormat = `${ext}+zip`; const formats = {
let downHref = bookInfo.link; [`${ext}+zip`]: `${bookInfo.link}/zip`,
[ext]: bookInfo.link,
};
if (ext === 'mobi') { if (ext === 'mobi') {
fileFormat = 'x-mobipocket-ebook'; formats['x-mobipocket-ebook'] = bookInfo.link;
} else if (ext == 'epub') { } else if (ext == 'epub') {
// formats[`${ext}+zip`] = bookInfo.link;
} else {
downHref = `${bookInfo.link}/zip`;
} }
//entry //entry
@@ -148,8 +147,13 @@ class BookPage extends BasePage {
title: bookInfo.book.title || 'Без названия', title: bookInfo.book.title || 'Без названия',
}); });
//author bookInfo
if (bookInfo.book.author) {
e.author = bookInfo.book.author.split(',').map(a => ({name: a}));
}
e['dc:language'] = bookInfo.book.lang; e['dc:language'] = bookInfo.book.lang;
e['dc:format'] = fileFormat; e['dc:format'] = ext;
//genre //genre
const genre = bookInfo.book.genre.split(','); const genre = bookInfo.book.genre.split(',');
@@ -173,7 +177,8 @@ class BookPage extends BasePage {
const infoObj = parser.bookInfo(); const infoObj = parser.bookInfo();
if (infoObj.titleInfo) { if (infoObj.titleInfo) {
if (infoObj.titleInfo.author.length) { //author fb2Info
if (!e.author && infoObj.titleInfo.author.length) {
e.author = infoObj.titleInfo.author.map(a => ({name: a})); e.author = infoObj.titleInfo.author.map(a => ({name: a}));
} }
@@ -190,12 +195,15 @@ class BookPage extends BasePage {
if (content) { if (content) {
e.content = { e.content = {
'*ATTRS': {type: 'text/html'}, '*ATTRS': {type: 'text/html'},
'*TEXT': he.escape(content), '*TEXT': this.escape(content),
}; };
} }
//links //links
e.link = [ this.downLink({href: downHref, type: `application/${fileFormat}`}) ]; e.link = [];
for (const [fileFormat, downHref] of Object.entries(formats))
e.link.push(this.downLink({href: downHref, type: `application/${fileFormat}`}));
if (bookInfo.cover) { if (bookInfo.cover) {
let coverType = 'image/jpeg'; let coverType = 'image/jpeg';
if (path.extname(bookInfo.cover) == '.png') if (path.extname(bookInfo.cover) == '.png')

View File

@@ -14,7 +14,7 @@ class GenrePage extends BasePage {
const query = { const query = {
from: req.query.from || 'search', from: req.query.from || 'search',
term: req.query.term || '*', term: req.query.term || '',
section: req.query.section || '', section: req.query.section || '',
}; };

View File

@@ -1,5 +1,3 @@
const he = require('he');
const BasePage = require('./BasePage'); const BasePage = require('./BasePage');
class SearchHelpPage extends BasePage { class SearchHelpPage extends BasePage {
@@ -45,7 +43,7 @@ class SearchHelpPage extends BasePage {
title: this.title, title: this.title,
content: { content: {
'*ATTRS': {type: 'text/html'}, '*ATTRS': {type: 'text/html'},
'*TEXT': he.escape(content), '*TEXT': this.escape(content),
}, },
link: [ link: [
this.downLink({href: '/book/fake-link', type: `application/fb2+zip`}) this.downLink({href: '/book/fake-link', type: `application/fb2+zip`})

View File

@@ -1,5 +1,6 @@
const BasePage = require('./BasePage'); const BasePage = require('./BasePage');
const utils = require('../utils'); const utils = require('../utils');
const iconv = require('iconv-lite');
class SearchPage extends BasePage { class SearchPage extends BasePage {
constructor(config) { constructor(config) {
@@ -28,7 +29,14 @@ class SearchPage extends BasePage {
const limit = 100; const limit = 100;
const offset = (page - 1)*limit; const offset = (page - 1)*limit;
const queryRes = await this.webWorker.search(from, {[from]: query.term, genre: query.genre, del: '0', offset, limit});
const searchQuery = {[from]: query.term, genre: query.genre, del: '0', offset, limit};
let queryRes = await this.webWorker.search(from, searchQuery);
if (queryRes.totalFound === 0) { // не нашли ничего, проверим, может term в кодировке ISO-8859-1 (баг koreader)
searchQuery[from] = iconv.encode(query.term, 'ISO-8859-1').toString();
queryRes = await this.webWorker.search(from, searchQuery);
}
const found = queryRes.found; const found = queryRes.found;

View File

@@ -11,6 +11,8 @@ const OpensearchPage = require('./OpensearchPage');
const SearchPage = require('./SearchPage'); const SearchPage = require('./SearchPage');
const SearchHelpPage = require('./SearchHelpPage'); const SearchHelpPage = require('./SearchHelpPage');
const log = new (require('../AppLogger'))().log;//singleton
module.exports = function(app, config) { module.exports = function(app, config) {
if (!config.opds || !config.opds.enabled) if (!config.opds || !config.opds.enabled)
return; return;
@@ -63,10 +65,8 @@ module.exports = function(app, config) {
next(); next();
} }
} catch (e) { } catch (e) {
log(LM_ERR, `OPDS: ${e.message}, url: ${req.originalUrl}`);
res.status(500).send({error: e.message}); res.status(500).send({error: e.message});
if (config.branch == 'development') {
console.error({error: e.message, url: req.originalUrl});
}
} }
}; };