Работа над opds
This commit is contained in:
@@ -534,28 +534,105 @@ class DbSearcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAuthorBookList(authorId) {
|
async opdsQuery(from, query) {
|
||||||
if (this.closed)
|
if (this.closed)
|
||||||
throw new Error('DbSearcher closed');
|
throw new Error('DbSearcher closed');
|
||||||
|
|
||||||
if (!authorId)
|
if (!['author', 'series', 'title'].includes(from))
|
||||||
|
throw new Error(`Unknown value for param 'from'`);
|
||||||
|
|
||||||
|
this.searchFlag++;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const db = this.db;
|
||||||
|
|
||||||
|
const queryKey = this.queryKey(query);
|
||||||
|
const opdsKey = `${from}-opds-${queryKey}`;
|
||||||
|
let result = await this.getCached(opdsKey);
|
||||||
|
|
||||||
|
if (result === null) {
|
||||||
|
const ids = await this.selectTableIds(from, query);
|
||||||
|
|
||||||
|
const totalFound = ids.length;
|
||||||
|
const depth = query.depth || 1;
|
||||||
|
|
||||||
|
//группировка по name длиной depth
|
||||||
|
const found = await db.select({
|
||||||
|
table: from,
|
||||||
|
rawResult: true,
|
||||||
|
where: `
|
||||||
|
const depth = ${db.esc(depth)};
|
||||||
|
const group = new Map();
|
||||||
|
|
||||||
|
const ids = ${db.esc(Array.from(ids))};
|
||||||
|
for (const id of ids) {
|
||||||
|
const row = @unsafeRow(id);
|
||||||
|
const s = row.name.substring(0, depth);
|
||||||
|
let g = group.get(s);
|
||||||
|
if (!g) {
|
||||||
|
g = {id: row.id, name: s, count: 0};
|
||||||
|
group.set(s, g);
|
||||||
|
}
|
||||||
|
g.count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = Array.from(group.values());
|
||||||
|
result.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
result = {found: found[0].rawResult, totalFound};
|
||||||
|
|
||||||
|
await this.putCached(opdsKey, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
this.searchFlag--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAuthorBookList(authorId, author) {
|
||||||
|
if (this.closed)
|
||||||
|
throw new Error('DbSearcher closed');
|
||||||
|
|
||||||
|
if (!authorId && !author)
|
||||||
return {author: '', books: ''};
|
return {author: '', books: ''};
|
||||||
|
|
||||||
this.searchFlag++;
|
this.searchFlag++;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//выборка книг автора по authorId
|
const db = this.db;
|
||||||
const rows = await this.restoreBooks('author', [authorId])
|
|
||||||
|
|
||||||
let author = '';
|
if (!authorId) {
|
||||||
|
//восстановим authorId
|
||||||
|
authorId = 0;
|
||||||
|
author = author.toLowerCase();
|
||||||
|
|
||||||
|
const rows = await db.select({
|
||||||
|
table: 'author',
|
||||||
|
rawResult: true,
|
||||||
|
where: `return Array.from(@dirtyIndexLR('value', ${db.esc(author)}, ${db.esc(author)}))`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rows.length && rows[0].rawResult.length)
|
||||||
|
authorId = rows[0].rawResult[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
//выборка книг автора по authorId
|
||||||
|
const rows = await this.restoreBooks('author', [authorId]);
|
||||||
|
|
||||||
|
let authorName = '';
|
||||||
let books = '';
|
let books = '';
|
||||||
|
|
||||||
if (rows.length) {
|
if (rows.length) {
|
||||||
author = rows[0].name;
|
authorName = rows[0].name;
|
||||||
books = rows[0].books;
|
books = rows[0].books;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {author, books: (books && books.length ? JSON.stringify(books) : '')};
|
return {author: authorName, books: (books && books.length ? JSON.stringify(books) : '')};
|
||||||
} finally {
|
} finally {
|
||||||
this.searchFlag--;
|
this.searchFlag--;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,10 +267,16 @@ class WebWorker {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAuthorBookList(authorId) {
|
async opdsQuery(from, query) {
|
||||||
this.checkMyState();
|
this.checkMyState();
|
||||||
|
|
||||||
return await this.dbSearcher.getAuthorBookList(authorId);
|
return await this.dbSearcher.opdsQuery(from, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAuthorBookList(authorId, author) {
|
||||||
|
this.checkMyState();
|
||||||
|
|
||||||
|
return await this.dbSearcher.getAuthorBookList(authorId, author);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSeriesBookList(series) {
|
async getSeriesBookList(series) {
|
||||||
|
|||||||
@@ -8,12 +8,66 @@ class AuthorPage extends BasePage {
|
|||||||
this.title = 'Авторы';
|
this.title = 'Авторы';
|
||||||
}
|
}
|
||||||
|
|
||||||
async body() {
|
bookAuthor(author) {
|
||||||
|
if (author) {
|
||||||
|
let a = author.split(',');
|
||||||
|
return a.slice(0, 3).join(', ') + (a.length > 3 ? ' и др.' : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async body(req) {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
result.entry = [
|
const query = {author: '', depth: 1, del: 0, limit: 100};
|
||||||
];
|
if (req.query.author) {
|
||||||
|
query.author = req.query.author;
|
||||||
|
query.depth = query.author.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query.author == '___others') {
|
||||||
|
query.author = '';
|
||||||
|
query.depth = 1;
|
||||||
|
query.others = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = [];
|
||||||
|
if (query.author && query.author[0] == '=') {
|
||||||
|
//книги по автору
|
||||||
|
const bookList = await this.webWorker.getAuthorBookList(0, query.author.substring(1));
|
||||||
|
|
||||||
|
if (bookList.books) {
|
||||||
|
const books = JSON.parse(bookList.books);
|
||||||
|
|
||||||
|
for (const book of books) {
|
||||||
|
const title = book.title || 'Без названия';
|
||||||
|
entry.push(
|
||||||
|
this.makeEntry({
|
||||||
|
id: book._uid,
|
||||||
|
title,
|
||||||
|
link: this.navLink({rel: 'subsection', href: `/${this.id}?book=${book._uid}`}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//поиск по каталогу
|
||||||
|
const queryRes = await this.opdsQuery('author', query);
|
||||||
|
|
||||||
|
for (const rec of queryRes) {
|
||||||
|
console.log(rec);
|
||||||
|
entry.push(
|
||||||
|
this.makeEntry({
|
||||||
|
id: rec.id,
|
||||||
|
title: this.bookAuthor(rec.title),//${(query.depth > 1 && rec.count ? ` (${rec.count})` : '')}
|
||||||
|
link: this.navLink({rel: 'subsection', href: `/${this.id}?author=${rec.q}`}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.entry = entry;
|
||||||
return this.makeBody(result);
|
return this.makeBody(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
|
const he = require('he');
|
||||||
|
|
||||||
const WebWorker = require('../WebWorker');//singleton
|
const WebWorker = require('../WebWorker');//singleton
|
||||||
const XmlParser = require('../xml/XmlParser');
|
const XmlParser = require('../xml/XmlParser');
|
||||||
|
|
||||||
|
const spaceChar = String.fromCodePoint(0x00B7);
|
||||||
|
const ruAlphabet = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя';
|
||||||
|
const enAlphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
const enruArr = (ruAlphabet + enAlphabet).split('');
|
||||||
|
const enru = new Set(enruArr);
|
||||||
|
|
||||||
class BasePage {
|
class BasePage {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
@@ -16,6 +24,8 @@ class BasePage {
|
|||||||
if (!entry.title)
|
if (!entry.title)
|
||||||
throw new Error('makeEntry: no title');
|
throw new Error('makeEntry: no title');
|
||||||
|
|
||||||
|
entry.title = he.escape(entry.title);
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
updated: (new Date()).toISOString().substring(0, 19) + 'Z',
|
updated: (new Date()).toISOString().substring(0, 19) + 'Z',
|
||||||
};
|
};
|
||||||
@@ -73,6 +83,62 @@ class BasePage {
|
|||||||
async body() {
|
async body() {
|
||||||
throw new Error('Body not implemented');
|
throw new Error('Body not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- stuff -------------------------------------------
|
||||||
|
async search(from, query) {
|
||||||
|
const result = [];
|
||||||
|
const queryRes = await this.webWorker.search(from, query);
|
||||||
|
|
||||||
|
for (const row of queryRes.found) {
|
||||||
|
const rec = {
|
||||||
|
id: row.id,
|
||||||
|
title: '=' + (row[from] || 'Без имени'),
|
||||||
|
q: `=${encodeURIComponent(row[from])}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
result.push(rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async opdsQuery(from, query) {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
const queryRes = await this.webWorker.opdsQuery(from, query);
|
||||||
|
let count = 0;
|
||||||
|
for (const row of queryRes.found)
|
||||||
|
count += row.count;
|
||||||
|
|
||||||
|
if (count <= query.limit)
|
||||||
|
return await this.search(from, query);
|
||||||
|
|
||||||
|
const names = new Set();
|
||||||
|
const others = [];
|
||||||
|
for (const row of queryRes.found) {
|
||||||
|
const name = row.name.toUpperCase();
|
||||||
|
|
||||||
|
if (!names.has(name)) {
|
||||||
|
const rec = {
|
||||||
|
id: row.id,
|
||||||
|
title: name.replace(/ /g, spaceChar),
|
||||||
|
q: encodeURIComponent(row.name.toLowerCase()),
|
||||||
|
count: row.count,
|
||||||
|
};
|
||||||
|
if (query.depth > 1 || enru.has(row.name[0].toLowerCase())) {
|
||||||
|
result.push(rec);
|
||||||
|
} else {
|
||||||
|
others.push(rec);
|
||||||
|
}
|
||||||
|
names.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.others && query.depth == 1)
|
||||||
|
result.push({id: 'other', title: 'Все остальные', q: '___others'});
|
||||||
|
|
||||||
|
return (!query.others ? result : others);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BasePage;
|
module.exports = BasePage;
|
||||||
@@ -13,10 +13,9 @@ class RootPage extends BasePage {
|
|||||||
|
|
||||||
async body() {
|
async body() {
|
||||||
const result = {};
|
const result = {};
|
||||||
const ww = this.webWorker;
|
|
||||||
|
|
||||||
if (!this.title) {
|
if (!this.title) {
|
||||||
const dbConfig = await ww.dbConfig();
|
const dbConfig = await this.webWorker.dbConfig();
|
||||||
const collection = dbConfig.inpxInfo.collection.split('\n');
|
const collection = dbConfig.inpxInfo.collection.split('\n');
|
||||||
this.title = collection[0].trim();
|
this.title = collection[0].trim();
|
||||||
if (!this.title)
|
if (!this.title)
|
||||||
|
|||||||
Reference in New Issue
Block a user