Skip to content

Commit

Permalink
Merge branch 'release/1.4.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
bookpauk committed Dec 21, 2022
2 parents 7fe0bce + b34d915 commit ac1be21
Show file tree
Hide file tree
Showing 16 changed files with 122 additions and 50 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/node_modules
/server/.inpx-web*
/server/inpx-web-filter.json
/dist
dev*.sh
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
1.4.1 / 2022-12-21
------------------

- Добавлена возможность поиска по регулярному выражению (префикс "~")
- Заплатка для исправления (#10)

1.4.0 / 2022-12-07
------------------

- Добавлена возможность расширенного поиска (раздел "</>"). Поиск не оптимизирован и может сильно нагружать сервер.
Отключить можно в конфиге, параметр extendedSearch
- Улучшение поддержки reverse-proxy, в конфиг добавлены параметры server.root и opds.root для встраивания inpx-web в уже существующий веб-сервер
Expand Down
8 changes: 6 additions & 2 deletions client/components/Search/AuthorList/AuthorList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,12 @@ class AuthorList extends BaseList {
result = `${count}`;
if (item.seriesLoaded) {
const rec = item.seriesLoaded[book.series];
const totalCount = (this.showDeleted ? rec.bookCount + rec.bookDelCount : rec.bookCount);
result += `/${totalCount}`;
// заплатка для исправления https://github.com/bookpauk/inpx-web/issues/10
// по невыясненным причинам rec иногда равен undefined
if (rec) {
const totalCount = (this.showDeleted ? rec.bookCount + rec.bookDelCount : rec.bookCount);
result += `/${totalCount}`;
}
}
return `(${result})`;
Expand Down
9 changes: 8 additions & 1 deletion client/components/Search/BaseList.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,14 @@ export default class BaseList {
} else if (searchValue[0] == '#') {

searchValue = searchValue.substring(1);
return !bookValue || (bookValue !== emptyFieldValue && !enru.has(bookValue[0]) && bookValue.indexOf(searchValue) >= 0);
if (!bookValue)
return false;
return bookValue !== emptyFieldValue && !enru.has(bookValue[0]) && bookValue.indexOf(searchValue) >= 0;
} else if (searchValue[0] == '~') {//RegExp

searchValue = searchValue.substring(1);
const re = new RegExp(searchValue, 'i');
return re.test(bookValue);
} else {
//where = `@dirtyIndexLR('value', ${db.esc(a)}, ${db.esc(a + maxUtf8Char)})`;
return bookValue.localeCompare(searchValue) >= 0 && bookValue.localeCompare(searchValue + maxUtf8Char) <= 0;
Expand Down
6 changes: 6 additions & 0 deletions client/components/Search/Search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ class Search {
this.list.liberamaReady = true;
this.sendMessage({type: 'mes', data: 'ready'});
this.sendCurrentUrl();
this.makeTitle();
break;
}
}
Expand Down Expand Up @@ -789,6 +790,11 @@ class Search {
Указание простого "#" в поиске по названию означает: найти всех авторов, названия книг которых начинаются не с русской или латинской буквы
</li>
<br>
<li>
"~" поиск по регулярному выражению. Например, для "~^\\s" в поле названия, будут найдены
все книги, названия которых начинаются с пробельного символа
</li>
<br>
<li>
"?" поиск пустых значений или тех, что начинаются с этого символа. Например, "?" в поле серии означает: найти всех авторов, у которых есть книги без серий
или название серии начинается с "?".
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ class SelectExtSearchDialog {
<li>
префикс "#": поиск подстроки в строке, но только среди начинающихся не с латинского или кириллического символа
</li>
<li>
префикс "~": поиск по регулярному выражению
</li>
<li>
префикс "?": поиск пустых значений или тех, что начинаются с этого символа
</li>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "inpx-web",
"version": "1.4.0",
"version": "1.4.1",
"author": "Book Pauk <bookpauk@gmail.com>",
"license": "CC0-1.0",
"repository": "bookpauk/inpx-web",
Expand Down
2 changes: 1 addition & 1 deletion server/config/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = {

//поправить в случае, если были критические изменения в DbCreator или InpxParser
//иначе будет рассинхронизация между сервером и клиентом на уровне БД
dbVersion: '10',
dbVersion: '11',
dbCacheSize: 5,

maxPayloadSize: 500,//in MB
Expand Down
11 changes: 9 additions & 2 deletions server/core/DbCreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ class DbCreator {
//сохраним поисковые таблицы
const chunkSize = 10000;

const saveTable = async(table, arr, nullArr, indexType = 'string') => {
const saveTable = async(table, arr, nullArr, indexType = 'string', delEmpty = false) => {

if (indexType == 'string')
arr.sort((a, b) => a.value.localeCompare(b.value));
Expand Down Expand Up @@ -366,6 +366,13 @@ class DbCreator {
callback({progress: i/arr.length});
}

if (delEmpty) {
const delResult = await db.delete({table, where: `@@indexLR('value', '?', '?')`});
const statField = `${table}Count`;
if (stats[statField])
stats[statField] -= delResult.deleted;
}

nullArr();
await db.close({table});
utils.freeMemory();
Expand All @@ -378,7 +385,7 @@ class DbCreator {

//series
callback({job: 'series save', jobMessage: 'Сохранение индекса серий', jobStep: 4, progress: 0});
await saveTable('series', seriesArr, () => {seriesArr = null});
await saveTable('series', seriesArr, () => {seriesArr = null}, 'string', true);

//title
callback({job: 'title save', jobMessage: 'Сохранение индекса названий', jobStep: 5, progress: 0});
Expand Down
29 changes: 24 additions & 5 deletions server/core/DbSearcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,18 @@ class DbSearcher {
a = a.substring(1);
where = `@indexIter('value', (v) => {
const enru = new Set(${db.esc(enruArr)});
return !v || (v !== ${db.esc(emptyFieldValue)} && !enru.has(v[0]) && v.indexOf(${db.esc(a)}) >= 0);
if (!v)
return false;
return v !== ${db.esc(emptyFieldValue)} && !enru.has(v[0]) && v.indexOf(${db.esc(a)}) >= 0;
})`;
} else if (a[0] == '~') {//RegExp
a = a.substring(1);
where = `
await (async() => {
const re = new RegExp(${db.esc(a)}, 'i');
@@indexIter('value', (v) => re.test(v) );
})()
`;
} else {
where = `@dirtyIndexLR('value', ${db.esc(a)}, ${db.esc(a + maxUtf8Char)})`;
}
Expand Down Expand Up @@ -99,7 +109,7 @@ class DbSearcher {
};

//авторы
if (query.author && query.author !== '*') {
if (query.author) {
const key = `book-ids-author-${query.author}`;
let ids = await this.getCached(key);

Expand All @@ -113,7 +123,7 @@ class DbSearcher {
}

//серии
if (query.series && query.series !== '*') {
if (query.series) {
const key = `book-ids-series-${query.series}`;
let ids = await this.getCached(key);

Expand All @@ -127,7 +137,7 @@ class DbSearcher {
}

//названия
if (query.title && query.title !== '*') {
if (query.title) {
const key = `book-ids-title-${query.title}`;
let ids = await this.getCached(key);

Expand Down Expand Up @@ -337,7 +347,7 @@ class DbSearcher {
//то в выборку по bookId могут попасть авторы, которые отсутствуют в критерии query.author,
//поэтому дополнительно фильтруем
let result = null;
if (from == 'author' && query.author && query.author !== '*') {
if (from == 'author' && query.author) {
const key = `filter-ids-author-${query.author}`;
let authorIds = await this.getCached(key);

Expand Down Expand Up @@ -562,6 +572,15 @@ class DbSearcher {

searchValue = searchValue.substring(1);
return `(row.${bookField} === '' || (!enru.has(row.${bookField}.toLowerCase()[0]) && row.${bookField}.toLowerCase().indexOf(${db.esc(searchValue)}) >= 0))`;
} else if (searchValue[0] == '~') {//RegExp
searchValue = searchValue.substring(1);

return `
(() => {
const re = new RegExp(${db.esc(searchValue)}, 'i');
return re.test(row.${bookField});
})()
`;
} else {

return `(row.${bookField}.toLowerCase().localeCompare(${db.esc(searchValue)}) >= 0 ` +
Expand Down
4 changes: 2 additions & 2 deletions server/core/ZipReader.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const StreamZip = require('node-stream-zip');
const StreamUnzip = require('node-stream-zip');

class ZipReader {
constructor() {
Expand All @@ -14,7 +14,7 @@ class ZipReader {
if (this.zip)
throw new Error('Zip file is already open');

const zip = new StreamZip.async({file: zipFile, skipEntryNameValidation: true});
const zip = new StreamUnzip.async({file: zipFile, skipEntryNameValidation: true});

if (zipEntries)
this.zipEntries = await zip.entries();
Expand Down
9 changes: 8 additions & 1 deletion server/core/opds/BasePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,14 @@ class BasePage {
} else if (searchValue[0] == '#') {

searchValue = searchValue.substring(1);
return !bookValue || (bookValue !== emptyFieldValue && !enru.has(bookValue[0]) && bookValue.indexOf(searchValue) >= 0);
if (!bookValue)
return false;
return bookValue !== emptyFieldValue && !enru.has(bookValue[0]) && bookValue.indexOf(searchValue) >= 0;
} else if (searchValue[0] == '~') {//RegExp

searchValue = searchValue.substring(1);
const re = new RegExp(searchValue, 'i');
return re.test(bookValue);
} else {
//where = `@dirtyIndexLR('value', ${db.esc(a)}, ${db.esc(a + maxUtf8Char)})`;
return bookValue.localeCompare(searchValue) >= 0 && bookValue.localeCompare(searchValue + maxUtf8Char) <= 0;
Expand Down
3 changes: 3 additions & 0 deletions server/core/opds/SearchHelpPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class SearchHelpPage extends BasePage {
<li>
префикс "#": поиск подстроки в строке, но только среди значений, начинающихся не с латинского или кириллического символа
</li>
<li>
префикс "~": поиск по регулярному выражению
</li>
<li>
префикс "?": поиск пустых значений или тех, что начинаются с этого символа
</li>
Expand Down
64 changes: 37 additions & 27 deletions server/core/opds/SearchPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,50 @@ class SearchPage extends BasePage {
let entry = [];
if (query.type) {
if (['author', 'series', 'title'].includes(query.type)) {
const from = query.type;
const page = query.page;
try {
const from = query.type;
const page = query.page;

const limit = 100;
const offset = (page - 1)*limit;
const queryRes = await this.webWorker.search(from, {[from]: query.term, del: 0, offset, limit});
const limit = 100;
const offset = (page - 1)*limit;
const queryRes = await this.webWorker.search(from, {[from]: query.term, del: 0, offset, limit});

const found = queryRes.found;
const found = queryRes.found;

for (let i = 0; i < found.length; i++) {
const row = found[i];
if (!row.bookCount)
continue;
for (let i = 0; i < found.length; i++) {
const row = found[i];
if (!row.bookCount)
continue;

entry.push(
this.makeEntry({
id: row.id,
title: `${(from === 'series' ? 'Серия: ': '')}${from === 'author' ? this.bookAuthor(row[from]) : row[from]}`,
link: this.navLink({href: `/${from}?${from}==${encodeURIComponent(row[from])}`}),
content: {
'*ATTRS': {type: 'text'},
'*TEXT': `${row.bookCount} книг${utils.wordEnding(row.bookCount, 8)}`,
},
}),
);
}
entry.push(
this.makeEntry({
id: row.id,
title: `${(from === 'series' ? 'Серия: ': '')}${from === 'author' ? this.bookAuthor(row[from]) : row[from]}`,
link: this.navLink({href: `/${from}?${from}==${encodeURIComponent(row[from])}`}),
content: {
'*ATTRS': {type: 'text'},
'*TEXT': `${row.bookCount} книг${utils.wordEnding(row.bookCount, 8)}`,
},
}),
);
}

if (queryRes.totalFound > offset + found.length) {
if (queryRes.totalFound > offset + found.length) {
entry.push(
this.makeEntry({
id: 'next_page',
title: '[Следующая страница]',
link: this.navLink({href: `/${this.id}?type=${from}&term=${encodeURIComponent(query.term)}&page=${page + 1}`}),
})
);
}
} catch(e) {
entry.push(
this.makeEntry({
id: 'next_page',
title: '[Следующая страница]',
link: this.navLink({href: `/${this.id}?type=${from}&term=${encodeURIComponent(query.term)}&page=${page + 1}`}),
}),
id: 'error',
title: `Ошибка: ${e.message}`,
link: this.navLink({href: `/fake-error-link`}),
})
);
}
}
Expand Down
10 changes: 5 additions & 5 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ let branch = '';
const argvStrings = ['host', 'port', 'app-dir', 'lib-dir', 'inpx'];

function showHelp(defaultConfig) {
console.log(utils.versionText(config));
console.log(utils.versionText(defaultConfig));
console.log(
`Usage: ${config.name} [options]
`Usage: ${defaultConfig.name} [options]
Options:
--help Print ${config.name} command line options
--help Print ${defaultConfig.name} command line options
--host=<ip> Set web server host, default: ${defaultConfig.server.host}
--port=<port> Set web server port, default: ${defaultConfig.server.port}
--app-dir=<dirpath> Set application working directory, default: <execDir>/.${config.name}
--lib-dir=<dirpath> Set library directory, default: the same as ${config.name} executable's
--app-dir=<dirpath> Set application working directory, default: <execDir>/.${defaultConfig.name}
--lib-dir=<dirpath> Set library directory, default: the same as ${defaultConfig.name} executable's
--inpx=<filepath> Set INPX collection file, default: the one that found in library dir
--recreate Force recreation of the search database on start
`
Expand Down

0 comments on commit ac1be21

Please sign in to comment.