diff --git a/.gitignore b/.gitignore
index d012435..c339748 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
/node_modules
/server/.inpx-web*
-/server/inpx-web-filter.json
/dist
dev*.sh
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b98d93c..9ed7392 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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 в уже существующий веб-сервер
diff --git a/client/components/Search/AuthorList/AuthorList.vue b/client/components/Search/AuthorList/AuthorList.vue
index 2f5892a..bc22ac2 100644
--- a/client/components/Search/AuthorList/AuthorList.vue
+++ b/client/components/Search/AuthorList/AuthorList.vue
@@ -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})`;
diff --git a/client/components/Search/BaseList.js b/client/components/Search/BaseList.js
index dee8119..ddc7b9d 100644
--- a/client/components/Search/BaseList.js
+++ b/client/components/Search/BaseList.js
@@ -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;
diff --git a/client/components/Search/Search.vue b/client/components/Search/Search.vue
index 05309bc..7d3700a 100644
--- a/client/components/Search/Search.vue
+++ b/client/components/Search/Search.vue
@@ -593,6 +593,7 @@ class Search {
this.list.liberamaReady = true;
this.sendMessage({type: 'mes', data: 'ready'});
this.sendCurrentUrl();
+ this.makeTitle();
break;
}
}
@@ -789,6 +790,11 @@ class Search {
Указание простого "#" в поиске по названию означает: найти всех авторов, названия книг которых начинаются не с русской или латинской буквы
+
+ "~" поиск по регулярному выражению. Например, для "~^\\s" в поле названия, будут найдены
+ все книги, названия которых начинаются с пробельного символа
+
+
"?" поиск пустых значений или тех, что начинаются с этого символа. Например, "?" в поле серии означает: найти всех авторов, у которых есть книги без серий
или название серии начинается с "?".
diff --git a/client/components/Search/SelectExtSearchDialog/SelectExtSearchDialog.vue b/client/components/Search/SelectExtSearchDialog/SelectExtSearchDialog.vue
index b4fe73d..197a9db 100644
--- a/client/components/Search/SelectExtSearchDialog/SelectExtSearchDialog.vue
+++ b/client/components/Search/SelectExtSearchDialog/SelectExtSearchDialog.vue
@@ -160,6 +160,9 @@ class SelectExtSearchDialog {
префикс "#": поиск подстроки в строке, но только среди начинающихся не с латинского или кириллического символа
+
+ префикс "~": поиск по регулярному выражению
+
префикс "?": поиск пустых значений или тех, что начинаются с этого символа
diff --git a/package-lock.json b/package-lock.json
index 1c6cb2a..fd29334 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "inpx-web",
- "version": "1.4.0",
+ "version": "1.4.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "inpx-web",
- "version": "1.4.0",
+ "version": "1.4.1",
"hasInstallScript": true,
"license": "CC0-1.0",
"dependencies": {
diff --git a/package.json b/package.json
index 51d0c9d..a664baf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "inpx-web",
- "version": "1.4.0",
+ "version": "1.4.1",
"author": "Book Pauk ",
"license": "CC0-1.0",
"repository": "bookpauk/inpx-web",
diff --git a/server/config/base.js b/server/config/base.js
index 45e882f..591f0a8 100644
--- a/server/config/base.js
+++ b/server/config/base.js
@@ -18,7 +18,7 @@ module.exports = {
//поправить в случае, если были критические изменения в DbCreator или InpxParser
//иначе будет рассинхронизация между сервером и клиентом на уровне БД
- dbVersion: '10',
+ dbVersion: '11',
dbCacheSize: 5,
maxPayloadSize: 500,//in MB
diff --git a/server/core/DbCreator.js b/server/core/DbCreator.js
index 0ed0cc3..4066ff7 100644
--- a/server/core/DbCreator.js
+++ b/server/core/DbCreator.js
@@ -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));
@@ -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();
@@ -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});
diff --git a/server/core/DbSearcher.js b/server/core/DbSearcher.js
index 49c7b48..79cceb2 100644
--- a/server/core/DbSearcher.js
+++ b/server/core/DbSearcher.js
@@ -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)})`;
}
@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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 ` +
diff --git a/server/core/ZipReader.js b/server/core/ZipReader.js
index 83e3532..614c61c 100644
--- a/server/core/ZipReader.js
+++ b/server/core/ZipReader.js
@@ -1,4 +1,4 @@
-const StreamZip = require('node-stream-zip');
+const StreamUnzip = require('node-stream-zip');
class ZipReader {
constructor() {
@@ -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();
diff --git a/server/core/opds/BasePage.js b/server/core/opds/BasePage.js
index ccfc4dc..fd5abe4 100644
--- a/server/core/opds/BasePage.js
+++ b/server/core/opds/BasePage.js
@@ -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;
diff --git a/server/core/opds/SearchHelpPage.js b/server/core/opds/SearchHelpPage.js
index 5c71751..60c084a 100644
--- a/server/core/opds/SearchHelpPage.js
+++ b/server/core/opds/SearchHelpPage.js
@@ -31,6 +31,9 @@ class SearchHelpPage extends BasePage {
префикс "#": поиск подстроки в строке, но только среди значений, начинающихся не с латинского или кириллического символа
+
+ префикс "~": поиск по регулярному выражению
+
префикс "?": поиск пустых значений или тех, что начинаются с этого символа
diff --git a/server/core/opds/SearchPage.js b/server/core/opds/SearchPage.js
index 93f3616..42026ec 100644
--- a/server/core/opds/SearchPage.js
+++ b/server/core/opds/SearchPage.js
@@ -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)}`,
+ },
+ }),
+ );
+ }
+
+ 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: 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) {
- 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`}),
+ })
);
}
}
diff --git a/server/index.js b/server/index.js
index 92427df..dcf5617 100644
--- a/server/index.js
+++ b/server/index.js
@@ -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= Set web server host, default: ${defaultConfig.server.host}
--port= Set web server port, default: ${defaultConfig.server.port}
- --app-dir= Set application working directory, default: /.${config.name}
- --lib-dir= Set library directory, default: the same as ${config.name} executable's
+ --app-dir= Set application working directory, default: /.${defaultConfig.name}
+ --lib-dir= Set library directory, default: the same as ${defaultConfig.name} executable's
--inpx= Set INPX collection file, default: the one that found in library dir
--recreate Force recreation of the search database on start
`