Bladeren bron

Merge branch 'release/1.3.3'

Book Pauk 2 jaren geleden
bovenliggende
commit
90c3faadcf

+ 48 - 0
CHANGELOG.md

@@ -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
+------------------
+
+- Добавлены разделы "Серии" и "Книги"
+- Расширена форма поиска: добавлен поиск по датам поступления и оценкам
+

+ 5 - 0
client/components/Api/Api.vue

@@ -239,6 +239,10 @@ class Api {
         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) {
         return await this.request({action: 'get-series-book-list', series});
     }
@@ -261,6 +265,7 @@ class Api {
 
     async logout() {
         await this.request({action: 'logout'});
+        this.accessGranted = false;
         await this.request({action: 'test'});
     }
 }

+ 21 - 4
client/components/Search/AuthorList/AuthorList.vue

@@ -56,7 +56,7 @@
                             </div>
 
                             <div class="q-ml-sm text-bold" style="color: #555">
-                                {{ getSeriesBookCount(book) }}
+                                {{ getSeriesBookCount(item, book) }}
                             </div>
                         </div>
 
@@ -188,15 +188,17 @@ class AuthorList extends BaseList {
         return `(${result})`;
     }
 
-    getSeriesBookCount(book) {
+    getSeriesBookCount(item, book) {
         let result = '';
         if (!this.showCounts || book.type != 'series')
             return result;
 
         let count = book.seriesBooks.length;
         result = `${count}`;
-        if (book.allBooksLoaded) {
-            result += `/${book.allBooksLoaded.length}`;
+        if (item.seriesLoaded) {
+            const rec = item.seriesLoaded[book.series];
+            const totalCount = (this.showDeleted ? rec.bookCount + rec.bookDelCount : rec.bookCount);
+            result += `/${totalCount}`;
         }
 
         return `(${result})`;
@@ -227,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) {
         if (item.books) {
             if (item.count > this.maxItemCount) {
@@ -328,6 +343,7 @@ class AuthorList extends BaseList {
             }
 
             item.booksLoaded = books;
+            this.getAuthorSeries(item);//no await
             this.showMore(item);
 
             await this.$nextTick();
@@ -360,6 +376,7 @@ class AuthorList extends BaseList {
                 name: rec.author.replace(/,/g, ', '),
                 count,
                 booksLoaded: false,
+                seriesLoaded: false,
                 books: false,
                 bookLoading: false,
                 showMore: false,

+ 25 - 2
client/components/Search/BaseList.js

@@ -253,7 +253,30 @@ export default class BaseList {
                 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) {
             this.$root.stdDialog.alert(e.message, 'Ошибка');
         }
@@ -276,7 +299,7 @@ export default class BaseList {
                 result = await this.api.getSeriesBookList(series);
             }
 
-            return (result.books ? JSON.parse(result.books) : []);
+            return result.books;
         } catch (e) {
             this.$root.stdDialog.alert(e.message, 'Ошибка');
         }

+ 10 - 4
client/components/Search/authorBooksStorage.js

@@ -8,6 +8,8 @@ const abStore = localForage.createInstance({
     name: 'authorBooksStorage'
 });
 
+const storageVersion = '1';
+
 class AuthorBooksStorage {
     constructor() {
     }
@@ -17,6 +19,8 @@ class AuthorBooksStorage {
     }
 
     async setData(key, data) {
+        key += storageVersion;
+
         if (typeof data !== 'string')
             throw new Error('AuthorBooksStorage: data must be a string');
 
@@ -25,6 +29,8 @@ class AuthorBooksStorage {
     }
 
     async getData(key) {
+        key += storageVersion;
+
         const item = await abStore.getItem(key);
         
         //обновим addTime
@@ -34,9 +40,9 @@ class AuthorBooksStorage {
         return item;
     }
 
-    async removeData(key) {
-        await abStore.removeItem(key);
-        await abStore.removeItem(`addTime-${key}`);
+    async _removeData(fullKey) {
+        await abStore.removeItem(fullKey);
+        await abStore.removeItem(`addTime-${fullKey}`);
     }
 
     async cleanStorage() {
@@ -62,7 +68,7 @@ class AuthorBooksStorage {
             }
 
             if (size > maxDataSize && toDel) {
-                await this.removeData(toDel);
+                await this._removeData(toDel);
             } else {
                 break;
             }

+ 2 - 2
package-lock.json

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

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "inpx-web",
-  "version": "1.3.2",
+  "version": "1.3.3",
   "author": "Book Pauk <bookpauk@gmail.com>",
   "license": "CC0-1.0",
   "repository": "bookpauk/inpx-web",

+ 8 - 0
server/controllers/WebSocketController.js

@@ -89,6 +89,8 @@ class WebSocketController {
                     await this.search(req, ws); break;
                 case 'get-author-book-list':
                     await this.getAuthorBookList(req, ws); break;
+                case 'get-author-series-list':
+                    await this.getAuthorSeriesList(req, ws); break;
                 case 'get-series-book-list':
                     await this.getSeriesBookList(req, ws); break;
                 case 'get-genre-tree':
@@ -169,6 +171,12 @@ class WebSocketController {
         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) {
         const result = await this.webWorker.getSeriesBookList(req.series);
 

+ 52 - 6
server/core/DbSearcher.js

@@ -599,7 +599,7 @@ class DbSearcher {
             throw new Error('DbSearcher closed');
 
         if (!authorId && !author)
-            return {author: '', books: ''};
+            return {author: '', books: []};
 
         this.searchFlag++;
 
@@ -625,14 +625,60 @@ class DbSearcher {
             const rows = await this.restoreBooks('author', [authorId]);
 
             let authorName = '';
-            let books = '';
+            let books = [];
 
             if (rows.length) {
                 authorName = rows[0].name;
                 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 {
             this.searchFlag--;
         }
@@ -643,7 +689,7 @@ class DbSearcher {
             throw new Error('DbSearcher closed');
 
         if (!series)
-            return {books: ''};
+            return {books: []};
 
         this.searchFlag++;
 
@@ -659,7 +705,7 @@ class DbSearcher {
                 where: `return Array.from(@dirtyIndexLR('value', ${db.esc(series)}, ${db.esc(series)}))`
             });
 
-            let books;
+            let books = [];
             if (rows.length && rows[0].rawResult.length) {
                 //выборка книг серии
                 const bookRows = await this.restoreBooks('series', [rows[0].rawResult[0]])
@@ -668,7 +714,7 @@ class DbSearcher {
                     books = bookRows[0].books;
             }
 
-            return {books: (books && books.length ? JSON.stringify(books) : '')};
+            return {books};
         } finally {
             this.searchFlag--;
         }

+ 6 - 3
server/core/RemoteLib.js

@@ -16,8 +16,6 @@ class RemoteLib {
             this.config = config;
 
             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://');
 
@@ -31,7 +29,7 @@ class RemoteLib {
         return instance;
     }
 
-    async wsRequest(query) {
+    async wsRequest(query, recurse = false) {
         if (this.accessToken)
             query.accessToken = this.accessToken;
 
@@ -40,6 +38,11 @@ class RemoteLib {
             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)
             throw new Error(response.error);
 

+ 10 - 0
server/core/WebAccess.js

@@ -1,6 +1,7 @@
 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 минут
@@ -13,6 +14,8 @@ class WebAccess {
         this.accessTimeout = config.accessTimeout*60*1000;
         this.accessMap = new Map();
 
+        asyncExit.add(this.closeDb.bind(this));
+
         setTimeout(() => { this.periodicClean(); }, cleanPeriod);
     }
 
@@ -67,6 +70,13 @@ class WebAccess {
         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 {

+ 10 - 4
server/core/WebWorker.js

@@ -11,7 +11,7 @@ const DbSearcher = require('./DbSearcher');
 const InpxHashCreator = require('./InpxHashCreator');
 const RemoteLib = require('./RemoteLib');//singleton
 
-const ayncExit = new (require('./AsyncExit'))();
+const asyncExit = new (require('./AsyncExit'))();
 const log = new (require('./AppLogger'))().log;//singleton
 const utils = require('./utils');
 const genreTree = require('./genres');
@@ -53,7 +53,7 @@ class WebWorker {
             this.db = null;
             this.dbSearcher = null;
 
-            ayncExit.add(this.closeDb.bind(this));
+            asyncExit.add(this.closeDb.bind(this));
 
             this.loadOrCreateDb();//no await
             this.periodicLogServerStats();//no await
@@ -221,7 +221,7 @@ class WebWorker {
             this.logServerStats();
         } catch (e) {
             log(LM_FATAL, e.message);            
-            ayncExit.exit(1);
+            asyncExit.exit(1);
         }
     }
 
@@ -279,6 +279,12 @@ class WebWorker {
         return await this.dbSearcher.getAuthorBookList(authorId, author);
     }
 
+    async getAuthorSeriesList(authorId) {
+        this.checkMyState();
+
+        return await this.dbSearcher.getAuthorSeriesList(authorId);
+    }
+
     async getSeriesBookList(series) {
         this.checkMyState();
 
@@ -628,7 +634,7 @@ class WebWorker {
             }
         } catch (e) {
             log(LM_FATAL, e.message);
-            ayncExit.exit(1);
+            asyncExit.exit(1);
         }
     }
 

+ 2 - 2
server/core/opds/AuthorPage.js

@@ -79,7 +79,7 @@ class AuthorPage extends BasePage {
             const bookList = await this.webWorker.getSeriesBookList(query.series);
 
             if (bookList.books) {
-                let books = JSON.parse(bookList.books);
+                let books = bookList.books;
                 const booksAll = this.filterBooks(books, {del: 0});
                 const filtered = (query.all ? booksAll : this.filterBooks(books, query));
                 const sorted = this.sortSeriesBooks(filtered);
@@ -122,7 +122,7 @@ class AuthorPage extends BasePage {
             const bookList = await this.webWorker.getAuthorBookList(0, query.author.substring(1));
 
             if (bookList.books) {
-                let books = JSON.parse(bookList.books);
+                let books = bookList.books;
                 books = this.sortBooks(this.filterBooks(books, query));
 
                 for (const b of books) {

+ 1 - 1
server/core/opds/SeriesPage.js

@@ -44,7 +44,7 @@ class SeriesPage extends BasePage {
             const bookList = await this.webWorker.getSeriesBookList(query.series.substring(1));
 
             if (bookList.books) {
-                let books = JSON.parse(bookList.books);
+                let books = bookList.books;
                 const booksAll = this.filterBooks(books, {del: 0});
                 const filtered = (query.all ? booksAll : this.filterBooks(books, query));
                 const sorted = this.sortSeriesBooks(filtered);

+ 1 - 2
server/index.js

@@ -158,8 +158,7 @@ async function main() {
     opds(app, config);
     initStatic(app, config);
     
-    const WebAccess = require('./core/WebAccess');
-    const webAccess = new WebAccess(config);
+    const webAccess = new (require('./core/WebAccess'))(config);
     await webAccess.init();
 
     const { WebSocketController } = require('./controllers');