From 7e5ea30579910241062200d09a30662d88ab8fab Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Sun, 25 Sep 2022 16:12:54 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B4=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/controllers/WebSocketController.js | 11 +++ server/core/DbCreator.js | 3 + server/core/WebWorker.js | 93 ++++++++++++++++++++++- server/core/utils.js | 5 ++ server/index.js | 35 +++++++-- 5 files changed, 140 insertions(+), 7 deletions(-) diff --git a/server/controllers/WebSocketController.js b/server/controllers/WebSocketController.js index 43e7d5b..939270f 100644 --- a/server/controllers/WebSocketController.js +++ b/server/controllers/WebSocketController.js @@ -72,6 +72,8 @@ class WebSocketController { await this.getBookList(req, ws); break; case 'get-genre-tree': await this.getGenreTree(req, ws); break; + case 'get-book-link': + await this.getBookLink(req, ws); break; default: throw new Error(`Action not found: ${req.action}`); @@ -141,6 +143,15 @@ class WebSocketController { this.send(result, req, ws); } + + async getBookLink(req, ws) { + if (!utils.hasProp(req, 'bookPath')) + throw new Error(`bookPath is empty`); + + const result = await this.webWorker.getBookLink(req.bookPath); + + this.send(result, req, ws); + } } module.exports = WebSocketController; diff --git a/server/core/DbCreator.js b/server/core/DbCreator.js index 1a536f6..6becb12 100644 --- a/server/core/DbCreator.js +++ b/server/core/DbCreator.js @@ -442,6 +442,9 @@ class DbCreator { await db.create({table: 'query_cache'}); await db.create({table: 'query_time'}); + //кэш-таблица имен файлов и их хешей + await db.create({table: 'file_hash'}); + callback({job: 'done', jobMessage: ''}); } } diff --git a/server/core/WebWorker.js b/server/core/WebWorker.js index 60b1840..cca0a6a 100644 --- a/server/core/WebWorker.js +++ b/server/core/WebWorker.js @@ -1,7 +1,9 @@ const os = require('os'); +const path = require('path'); const fs = require('fs-extra'); const _ = require('lodash'); +const ZipReader = require('./ZipReader'); const WorkerState = require('./WorkerState'); const { JembaDbThread } = require('jembadb'); const DbCreator = require('./DbCreator'); @@ -263,6 +265,95 @@ class WebWorker { return result; } + async extractBook(bookPath) { + const tempDir = this.config.tempDir; + const outFile = `${tempDir}/${utils.randomHexString(30)}`; + + const folder = path.dirname(bookPath); + const file = path.basename(bookPath); + + const zipReader = new ZipReader(); + await zipReader.open(folder); + + try { + await zipReader.extractToFile(file, outFile); + return outFile; + } finally { + zipReader.close(); + } + } + + async restoreBook(bookPath) { + const db = this.db; + const publicDir = this.config.publicDir; + + const extractedFile = await this.extractBook(bookPath); + + const hash = await utils.getFileHash(extractedFile, 'sha256', 'hex'); + const link = `/files/${hash}`; + const publicPath = `${publicDir}${link}`; + + if (!await fs.pathExists(publicPath)) { + await fs.move(extractedFile, publicPath); + } else { + await fs.remove(extractedFile); + } + + await db.insert({ + table: 'file_hash', + replace: true, + rows: [ + {id: bookPath, hash}, + {id: hash, bookPath} + ] + }); + + return link; + } + + async getBookLink(bookPath) { + this.checkMyState(); + + const db = this.db; + const publicDir = this.config.publicDir; + let link = ''; + + //найдем хеш + const rows = await db.select({table: 'file_hash', where: `@@id(${db.esc(bookPath)})`}); + if (rows.length) {//хеш найден по bookPath + const hash = rows[0].hash; + link = `/files/${hash}`; + const publicPath = `${publicDir}${link}`; + + if (!await fs.pathExists(publicPath)) { + link = ''; + } + } + + if (!link) { + link = await this.restoreBook(bookPath) + } + + if (!link) + throw new Error('404 Файл не найден'); + + return {link}; + } + + async restoreBookFile(publicPath) { + const db = this.db; + const hash = path.basename(publicPath); + + //найдем bookPath + const rows = await db.select({table: 'file_hash', where: `@@id(${db.esc(hash)})`}); + if (rows.length) {//bookPath найден по хешу + const bookPath = rows[0].bookPath; + await this.restoreBook(bookPath); + } else {//bookPath не найден + throw new Error('404 Файл не найден'); + } + } + async logServerStats() { while (1) {// eslint-disable-line try { @@ -274,7 +365,7 @@ class WebWorker { } catch (e) { log(LM_ERR, e.message); } - await utils.sleep(5000); + await utils.sleep(5*1000); } } } diff --git a/server/core/utils.js b/server/core/utils.js index 0411130..d2c276a 100644 --- a/server/core/utils.js +++ b/server/core/utils.js @@ -89,6 +89,10 @@ function intersectSet(arrSet) { return result; } +function randomHexString(len) { + return crypto.randomBytes(len).toString('hex') +} + module.exports = { sleep, versionText, @@ -99,4 +103,5 @@ module.exports = { getFileHash, getBufHash, intersectSet, + randomHexString, }; \ No newline at end of file diff --git a/server/index.js b/server/index.js index 69cda7c..222667f 100644 --- a/server/index.js +++ b/server/index.js @@ -149,19 +149,42 @@ async function main() { }); } -function initStatic(app, config) {// eslint-disable-line - //загрузка файлов в /files - //TODO +function initStatic(app, config) { + const WebWorker = require('./core/WebWorker');//singleton + const webWorker = new WebWorker(config); + //загрузка или восстановление файлов в /files, при необходимости + app.use(async(req, res, next) => { + if ((req.method !== 'GET' && req.method !== 'HEAD') || + !(req.path.indexOf('/files/') === 0) + ) { + return next(); + } + + const publicPath = `${config.publicDir}${req.path}`; + + //восстановим + try { + if (!await fs.pathExists(publicPath)) { + await webWorker.restoreBookFile(publicPath); + } + } catch(e) { + log(LM_ERR, `static::restoreBookFile ${req.path} > ${e.message}`); + } + + return next(); + }); + + //заголовки при отдаче + const filesDir = `${config.publicDir}/files`; app.use(express.static(config.publicDir, { maxAge: '30d', - /*setHeaders: (res, filePath) => { + setHeaders: (res, filePath) => { if (path.dirname(filePath) == filesDir) { - res.set('Content-Type', 'application/xml'); res.set('Content-Encoding', 'gzip'); } - },*/ + }, })); }