Browse Source

Работа над проектом

Book Pauk 2 years ago
parent
commit
7e5ea30579

+ 11 - 0
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;

+ 3 - 0
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: ''});
     }
 }

+ 92 - 1
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);
         }
     }
 }

+ 5 - 0
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,
 };

+ 29 - 6
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');
             }
-        },*/
+        },
     }));
 }