diff --git a/CHANGELOG.md b/CHANGELOG.md index 9811028..2a677d0 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/README.md b/README.md index 64f88c7..c325110 100644 --- a/README.md +++ b/README.md @@ -66,29 +66,63 @@ OPDS-сервер доступен по адресу [http://127.0.0.1:12380/opd Usage: inpx-web [options] Options: - --help Показать опции командной строки - --host= Задать имя хоста для веб сервера, по умолчанию: 0.0.0.0 - --port= Задать порт для веб сервера, по умолчанию: 12380 - --app-dir= Задать рабочую директорию, по умолчанию: /.inpx-web - --lib-dir= Задать директорию библиотеки (с zip-архивами), по умолчанию: там же, где лежит файл приложения - --inpx= Задать путь к файлу .inpx, по умолчанию: тот, что найдется в директории библиотеки - --recreate Принудительно пересоздать поисковую БД при запуске приложения + --help Показать опции командной строки + --host= Задать имя хоста для веб сервера, по умолчанию: 0.0.0.0 + --port= Задать порт для веб сервера, по умолчанию: 12380 + --config= Задать файл конфигурации, по умолчанию: /config.json + --data-dir= (или --app-dir) Задать рабочую директорию, по умолчанию: /.inpx-web + --lib-dir= Задать директорию библиотеки (с zip-архивами), по умолчанию: там же, где лежит файл приложения + --inpx= Задать путь к файлу .inpx, по умолчанию: тот, что найдется в директории библиотеки + --recreate Принудительно пересоздать поисковую БД при запуске приложения + --unsafe-filter Использовать небезопасный фильтр на свой страх и риск ``` ### Конфигурация -При первом запуске в рабочей директории будет создан конфигурационный файл `config.json`: + +По умолчанию, при первом запуске в рабочей директории будет создан конфигурационный файл `config.json`. +При необходимости, можно настроить нужный параметр в этом файле вручную. Параметры командной +строки имеют больший приоритет, чем настройки из `config.json`. + ```js { + // рабочая директория приложения, аналог параметра командной строки --data-dir (или --app-dir) + // пустая строка: использовать значение по умолчанию - /.inpx-web + // где execDir - директория файла приложения + "dataDir": "", + + // директория для хранения временных файлов + // пустая строка: использовать значение по умолчанию - /tmp + // специальное значение "${OS}" указывается для использования системного каталога: + // "${OS}" => "/inpx-web" + "tempDir": "", + + // директория для хранения логов + // пустая строка: использовать значение по умолчанию - /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`. - ### Удаленная библиотека @@ -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` в конфиге. ### Настройка https с помощью nginx diff --git a/build/release.js b/build/release.js index 85839d1..44afb89 100644 --- a/build/release.js +++ b/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); diff --git a/client/components/Search/BaseList.js b/client/components/Search/BaseList.js index d1d98af..f8f7a34 100644 --- a/client/components/Search/BaseList.js +++ b/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') { diff --git a/package-lock.json b/package-lock.json index 77b19f1..94eead9 100644 --- a/package-lock.json +++ b/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": { diff --git a/package.json b/package.json index dd4a9d2..0b78227 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inpx-web", - "version": "1.5.6", + "version": "1.5.7", "author": "Book Pauk ", "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", diff --git a/server/config/base.js b/server/config/base.js index 0b36a97..ac76f8d 100644 --- a/server/config/base.js +++ b/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, diff --git a/server/config/index.js b/server/config/index.js index 7c8d0e7..fc170e1 100644 --- a/server/config/index.js +++ b/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); - } else { - config.dataDir = `${config.execDir}/.${config.name}`; + if (!tempDataDir) { + tempDataDir = `${config.execDir}/.${config.name}`; + } + + if (configFile) { + config.configFile = path.resolve(configFile); + } else { + 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)); } } diff --git a/server/core/WebWorker.js b/server/core/WebWorker.js index e9994ac..803f28e 100644 --- a/server/core/WebWorker.js +++ b/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}`); diff --git a/server/index.js b/server/index.js index a083fbe..0a4b847 100644 --- a/server/index.js +++ b/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= Set web server host, default: ${defaultConfig.server.host} - --port= Set web server port, default: ${defaultConfig.server.port} - --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 + --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} + --config= Set config filename, default: /config.json + --data-dir= (or --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 + --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) {