Эх сурвалжийг харах

Merge branch 'release/1.5.7'

Book Pauk 1 жил өмнө
parent
commit
7e268fec0b

+ 6 - 1
CHANGELOG.md

@@ -1,9 +1,14 @@
+1.5.7 / 2024-04-04
+
+- В параметр bookReadLink конфига добавлен вариант замены DOWNLOAD_URI на uri из ссылки для скачивания книги (#29)
+- В параметры командной строки добавлена возможность задавать путь к файлу конфигурации, а все остальные настройки приложения можно указать в нем самом (#30)
+
 1.5.6 / 2024-03-25
 
 - Добавлен вывод version.info в статистику по коллекции (#27)
 - Убрано (по умолчанию) ежеминутное журналирование статистики сервера. В конфиг добавлен параметр logServerStats (#26)
 - В конфиг добавлен параметр logQueries для журналирования запросов и времени их выполнения
-- Добавлена расшифровка имен жанров в информации о книге(#24)
+- Добавлена расшифровка имен жанров в информации о книге (#24)
 
 1.5.5 / 2023-04-25
 

+ 50 - 15
README.md

@@ -66,29 +66,63 @@ OPDS-сервер доступен по адресу [http://127.0.0.1:12380/opd
 Usage: inpx-web [options]
 
 Options:
-  --help              Показать опции командной строки
-  --host=<ip>         Задать имя хоста для веб сервера, по умолчанию: 0.0.0.0
-  --port=<port>       Задать порт для веб сервера, по умолчанию: 12380
-  --app-dir=<dirpath> Задать рабочую директорию, по умолчанию: <execDir>/.inpx-web
-  --lib-dir=<dirpath> Задать директорию библиотеки (с zip-архивами), по умолчанию: там же, где лежит файл приложения
-  --inpx=<filepath>   Задать путь к файлу .inpx, по умолчанию: тот, что найдется в директории библиотеки
-  --recreate          Принудительно пересоздать поисковую БД при запуске приложения
+  --help               Показать опции командной строки
+  --host=<ip>          Задать имя хоста для веб сервера, по умолчанию: 0.0.0.0
+  --port=<port>        Задать порт для веб сервера, по умолчанию: 12380
+  --config=<filepath>  Задать файл конфигурации, по умолчанию: <dataDir>/config.json
+  --data-dir=<dirpath> (или --app-dir) Задать рабочую директорию, по умолчанию: <execDir>/.inpx-web
+  --lib-dir=<dirpath>  Задать директорию библиотеки (с zip-архивами), по умолчанию: там же, где лежит файл приложения
+  --inpx=<filepath>    Задать путь к файлу .inpx, по умолчанию: тот, что найдется в директории библиотеки
+  --recreate           Принудительно пересоздать поисковую БД при запуске приложения
+  --unsafe-filter      Использовать небезопасный фильтр на свой страх и риск
 ```
 
 <a id="config" />
 
 ### Конфигурация
-При первом запуске в рабочей директории будет создан конфигурационный файл `config.json`:
+
+По умолчанию, при первом запуске в рабочей директории будет создан конфигурационный файл `config.json`.
+При необходимости, можно настроить нужный параметр в этом файле вручную. Параметры командной
+строки имеют больший приоритет, чем настройки из `config.json`.
+
 ```js
 {
+    // рабочая директория приложения, аналог параметра командной строки --data-dir (или --app-dir)
+    // пустая строка: использовать значение по умолчанию - <execDir>/.inpx-web
+    // где execDir - директория файла приложения
+    "dataDir": "",
+
+    // директория для хранения временных файлов
+    // пустая строка: использовать значение по умолчанию - <dataDir>/tmp
+    // специальное значение "${OS}" указывается для использования системного каталога:
+    // "${OS}" => "<os_temporary_dir>/inpx-web"
+    "tempDir": "",
+
+    // директория для хранения логов
+    // пустая строка: использовать значение по умолчанию - <dataDir>/logs
+    "logDir": "",
+
     // директория библиотеки (с zip-архивами), аналог параметра командной строки --lib-dir
-    // пустая строка: использовать значение по умолчанию - директорию файла приложения
+    // пустая строка: использовать значение по умолчанию - директорию файла приложения (execDir)
     "libDir": "",
 
     // путь к файлу .inpx, аналог параметра командной строки --inpx
     // пустая строка: использовать значение по умолчанию - inpx-файл, что найдется в директории библиотеки
     "inpx": "",
 
+    // конфигурационный файл для фильра по авторам и книгам (см. ниже)
+    // пустая строка: использовать значение по умолчанию - файл filter.json в директории файла конфигурации
+    "inpxFilterFile": "",
+
+    // разрешить(true)/запретить(false) перезаписывать файл конфигурации, если появились новые параметры для настройки
+    // файл перезаписывается с сохранением всех предыдущих настроек и с новыми по умолчанию
+    // бывает полезно при выходе новых версий приложения
+    "allowConfigRewrite": false,
+
+    // разрешить(true)/запретить(false) использовать небезопасный фильтр (см. ниже)
+    // аналог параметра командной строки --unsafe-filter
+    "allowUnsafeFilter": false,
+
     // пароль для ограничения доступа к веб-интерфейсу сервера
     // пустое значение - доступ без ограничений
     "accessPassword": "",
@@ -106,6 +140,8 @@ Options:
     // содержимое кнопки-ссылки "(читать)", если не задано - кнопка "(читать)" не показывается
     // пример: "https://omnireader.ru/#/reader?url=${DOWNLOAD_LINK}"
     // на место ${DOWNLOAD_LINK} будет подставлена ссылка на скачивание файла книги
+    // пример: "https://mydomain.ru/#/reader?url=http://127.0.0.1:8086${DOWNLOAD_URI}"
+    // на место ${DOWNLOAD_URI} будут подставлены параметры (без имени хоста) из ссылки на скачивание файла книги
     "bookReadLink": "",
 
     // включить(true)/выключить(false) журналирование
@@ -208,9 +244,6 @@ Options:
 }
 ```
 
-При необходимости, можно настроить нужный параметр в этом файле вручную. Параметры командной
-строки имеют больший приоритет, чем настройки из `config.json`.
-
 <a id="remotelib" />
 
 ### Удаленная библиотека
@@ -245,8 +278,8 @@ Options:
 ### Фильтр по авторам и книгам
 
 При создании поисковой БД, во время загрузки и парсинга .inpx-файла, имеется возможность
-отфильтровать авторов и книги, задав определенные критерии. Для этого небходимо создать
рабочей директории (там же, где `config.json`) файл `filter.json` следующего вида:
+отфильтровать авторов и книги, задав определенные критерии. По умолчанию, для этого небходимо создать
+в директории конфигурационного файла (там же, где `config.json`) файл `filter.json` следующего вида:
 ```json
 {
   "info": {
@@ -291,8 +324,10 @@ Options:
 }
 ```
 Использование `filter` небезопасно, т.к. позволяет выполнить произвольный js-код внутри программы,
-поэтому запуск приложения в этом случае должен сопровождаться дополнительным параметром командной строки `--unsafe-filter`.
+поэтому запуск приложения в этом случае должен сопровождаться дополнительным параметром командной строки `--unsafe-filter`
+или разрешением в конфиге `allowUnsafeFilter`.
 Названия атрибутов inpxRec соответствуют названиям в нижнем регистре из структуры structure.info в .inpx-файле.
+Файл `filter.json` можно расположить где угодно, что задается параметром `inpxFilterFile` в конфиге.
 <a id="https" />
 
 ### Настройка https с помощью nginx

+ 9 - 4
build/release.js

@@ -3,6 +3,7 @@ const path = require('path');
 const { execSync } = require('child_process');
 
 const pckg = require('../package.json');
+const platform = process.argv[2];
 
 const distDir = path.resolve(__dirname, '../dist');
 const outDir = `${distDir}/release`;
@@ -20,10 +21,14 @@ async function makeRelease(target) {
 async function main() {
     try {
         await fs.emptyDir(outDir);
-        await makeRelease('win');
-        await makeRelease('linux');
-        await makeRelease('linux-arm64');
-        await makeRelease('macos');
+        if (platform) {
+            await makeRelease(platform);
+        } else {
+            await makeRelease('win');
+            await makeRelease('linux');
+            await makeRelease('linux-arm64');
+            await makeRelease('macos');
+        }
     } catch(e) {
         console.error(e);
         process.exit(1);

+ 12 - 1
client/components/Search/BaseList.js

@@ -178,7 +178,18 @@ export default class BaseList {
                 if (this.list.liberamaReady) {
                     this.$emit('listEvent', {action: 'submitUrl', data: href});
                 } else {
-                    const url = this.config.bookReadLink.replace('${DOWNLOAD_LINK}', href);
+                    const bookReadLink = this.config.bookReadLink;
+                    let url = bookReadLink;
+
+                    if (bookReadLink.indexOf('${DOWNLOAD_LINK}') >= 0) {
+                        url = bookReadLink.replace('${DOWNLOAD_LINK}', href);
+
+                    } else if (bookReadLink.indexOf('${DOWNLOAD_URI}') >= 0) {
+                        const hrefUrl = new URL(href);
+                        const urlWithoutHost = hrefUrl.pathname + hrefUrl.search + hrefUrl.hash;
+                        url = bookReadLink.replace('${DOWNLOAD_URI}', urlWithoutHost);
+                    }
+
                     window.open(url, '_blank');
                 }
             } else if (action == 'bookInfo') {

+ 2 - 2
package-lock.json

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

+ 5 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "inpx-web",
-  "version": "1.5.6",
+  "version": "1.5.7",
   "author": "Book Pauk <bookpauk@gmail.com>",
   "license": "CC0-1.0",
   "repository": "bookpauk/inpx-web",
@@ -17,6 +17,10 @@
     "build:client-dev": "webpack --config build/webpack.dev.config.js",
     "build:all": "npm run build:linux && npm run build:win && npm run build:macos && npm run build:linux-arm64",
     "release": "npm run build:all && node build/release.js",
+    "release:linux": "npm run build:linux && node build/release.js linux",
+    "release:win": "npm run build:win && node build/release.js win",
+    "release:macos": "npm run build:macos && node build/release.js macos",
+    "release:arm64": "npm run build:linux-arm64 && node build/release.js arm64",
     "postinstall": "npm run build:client-dev"
   },
   "bin": "server/index.js",

+ 6 - 0
server/config/base.js

@@ -10,9 +10,15 @@ module.exports = {
     name: pckg.name,
 
     execDir,
+    dataDir: '',
+    tempDir: '',
+    logDir: '',
     libDir: '',
     inpx: '',
+    inpxFilterFile: '',
 
+    allowConfigRewrite: false,
+    allowUnsafeFilter: false,
     accessPassword: '',
     accessTimeout: 0,
     extendedSearch: true,

+ 25 - 23
server/config/index.js

@@ -5,8 +5,14 @@ const fs = require('fs-extra');
 const branchFilename = __dirname + '/application_env';
 
 const propsToSave = [
+    'dataDir',
+    'tempDir',
+    'logDir',
     'libDir',
     'inpx',
+    'inpxFilterFile',
+    'allowConfigRewrite',
+    'allowUnsafeFilter',
     'accessPassword',
     'accessTimeout',
     'extendedSearch',
@@ -46,7 +52,7 @@ class ConfigManager {
         return instance;
     }
 
-    async init(dataDir) {
+    async init(tempDataDir, configFile) {
         if (this.inited)
             throw new Error('already inited');
 
@@ -63,14 +69,16 @@ class ConfigManager {
         this.branchConfigFile = __dirname + `/${this.branch}.js`;
         const config = require(this.branchConfigFile);
 
-        if (dataDir) {
-            config.dataDir = path.resolve(dataDir);
+        if (!tempDataDir) {
+            tempDataDir = `${config.execDir}/.${config.name}`;
+        }
+
+        if (configFile) {
+            config.configFile = path.resolve(configFile);
         } else {
-            config.dataDir = `${config.execDir}/.${config.name}`;
+            config.configFile = `${tempDataDir}/config.json`;
         }
 
-        await fs.ensureDir(config.dataDir);
-        this._userConfigFile = `${config.dataDir}/config.json`;
         this._config = config;
 
         this.inited = true;
@@ -86,37 +94,31 @@ class ConfigManager {
         Object.assign(this._config, value);
     }
 
-    get userConfigFile() {
-        return this._userConfigFile;
-    }
-
-    set userConfigFile(value) {
-        if (value)
-            this._userConfigFile = value;
-    }
-
     async load() {
         try {
             if (!this.inited)
                 throw new Error('not inited');
 
-            if (await fs.pathExists(this.userConfigFile)) {
-                const data = JSON.parse(await fs.readFile(this.userConfigFile, 'utf8'));
+            if (await fs.pathExists(this._config.configFile)) {
+                const data = JSON.parse(await fs.readFile(this._config.configFile, 'utf8'));
                 const config = _.pick(data, propsToSave);
 
                 this.config = config;
 
                 //сохраним конфиг, если не все атрибуты присутствуют в файле конфига
-                for (const prop of propsToSave)
-                    if (!Object.prototype.hasOwnProperty.call(config, prop)) {
-                        await this.save();
-                        break;
+                if (config.allowConfigRewrite) {
+                    for (const prop of propsToSave) {
+                        if (!Object.prototype.hasOwnProperty.call(config, prop)) {
+                            await this.save();
+                            break;                        
+                        }
                     }
+                }
             } else {
                 await this.save();
             }
         } catch(e) {
-            throw new Error(`Error while loading "${this.userConfigFile}": ${e.message}`);
+            throw new Error(`Error while loading "${this._config.configFile}": ${e.message}`);
         }
     }
 
@@ -125,7 +127,7 @@ class ConfigManager {
             throw new Error('not inited');
 
         const dataToSave = _.pick(this._config, propsToSave);
-        await fs.writeFile(this.userConfigFile, JSON.stringify(dataToSave, null, 4));
+        await fs.writeFile(this._config.configFile, JSON.stringify(dataToSave, null, 4));
     }
 }
 

+ 5 - 4
server/core/WebWorker.js

@@ -659,8 +659,6 @@ class WebWorker {
             }
         }
 
-        log(LM_WARN, `clean dir ${dir}, maxSize=${maxSize}, found ${files.length} files, total size=${size}`);
-
         files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
 
         let i = 0;
@@ -673,7 +671,10 @@ class WebWorker {
             i++;
         }
 
-        log(LM_WARN, `removed ${i} files`);
+        if (i) {
+            log(LM_WARN, `clean dir ${dir}, maxSize=${maxSize}, found ${files.length} files, total size=${size}`);
+            log(LM_WARN, `removed ${i} files`);
+        }
     }
 
     async periodicCleanDir(dirConfig) {
@@ -727,7 +728,7 @@ class WebWorker {
                     log('inpx file: changes found, recreating DB');
                     await this.recreateDb();
                 } else {
-                    log('inpx file: no changes');
+                    //log('inpx file: no changes');
                 }
             } catch(e) {
                 log(LM_ERR, `periodicCheckInpx: ${e.message}`);

+ 30 - 14
server/index.js

@@ -1,5 +1,6 @@
 const fs = require('fs-extra');
 const path = require('path');
+const os = require('os');
 
 const express = require('express');
 const http = require('http');
@@ -13,7 +14,7 @@ let log;
 let config;
 let argv;
 let branch = '';
-const argvStrings = ['host', 'port', 'app-dir', 'lib-dir', 'inpx'];
+const argvStrings = ['host', 'port', 'config', 'data-dir', 'app-dir', 'lib-dir', 'inpx'];
 
 function showHelp(defaultConfig) {
     console.log(utils.versionText(defaultConfig));
@@ -21,24 +22,27 @@ function showHelp(defaultConfig) {
 `Usage: ${defaultConfig.name} [options]
 
 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>/.${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
+  --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}
+  --config=<filepath>  Set config filename, default: <dataDir>/config.json
+  --data-dir=<dirpath> (or --app-dir) 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
+  --unsafe-filter      Use filter config at your own risk
 `
     );
 }
 
 async function init() {
     argv = require('minimist')(process.argv.slice(2), {string: argvStrings});
-    const dataDir = argv['app-dir'];
+    const argvDataDir = argv['data-dir'] || argv['app-dir'];
+    const configFile = argv['config'];
 
     //config
     const configManager = new (require('./config'))();//singleton
-    await configManager.init(dataDir);
+    await configManager.init(argvDataDir, configFile);
     const defaultConfig = configManager.config;
 
     await configManager.load();
@@ -46,8 +50,12 @@ async function init() {
     branch = config.branch;
 
     //dirs
-    config.tempDir = `${config.dataDir}/tmp`;
-    config.logDir = `${config.dataDir}/log`;
+    config.dataDir = config.dataDir || argvDataDir || `${config.execDir}/.${config.name}`;
+    config.tempDir = config.tempDir || `${config.dataDir}/tmp`;
+    if (config.tempDir === '${OS}')
+        config.tempDir = `${os.tmpdir()}/${config.name}`
+
+    config.logDir = config.logDir || `${config.dataDir}/log`;    
     config.publicDir = `${config.dataDir}/public`;
     config.publicFilesDir = `${config.dataDir}/public-files`;
     config.rootPathStatic = config.server.root || '';
@@ -127,14 +135,22 @@ async function init() {
     }
 
     config.recreateDb = argv.recreate || false;
-    config.inpxFilterFile = `${config.dataDir}/filter.json`;
-    config.allowUnsafeFilter = argv['unsafe-filter'] || false;
+    config.inpxFilterFile = config.inpxFilterFile || `${path.dirname(config.configFile)}/filter.json`;
+    config.allowUnsafeFilter = argv['unsafe-filter'] || config.allowUnsafeFilter || false;
 
     //web app
     if (branch !== 'development') {
         const createWebApp = require('./createWebApp');
         await createWebApp(config);
     }
+
+    //log dirs
+    for (const prop of ['configFile', 'dataDir', 'tempDir', 'logDir']) {
+        log(`${prop}: ${config[prop]}`);
+    }
+
+    if (await fs.pathExists(config.inpxFilterFile))
+        log(`inpxFilterFile: ${config.inpxFilterFile}`)
 }
 
 function logQueries(app) {