Browse Source

Добавлено формирование zip-файла (#4)

Book Pauk 2 years ago
parent
commit
b3ed9ea89c
3 changed files with 101 additions and 55 deletions
  1. 14 2
      server/core/opds/BookPage.js
  2. 8 6
      server/core/utils.js
  3. 79 47
      server/static.js

+ 14 - 2
server/core/opds/BookPage.js

@@ -128,7 +128,19 @@ class BookPage extends BasePage {
 
             if (bookInfo) {
                 const {genreMap} = await this.getGenres();
-                const fileFormat = `${bookInfo.book.ext}+zip`;
+
+                //format
+                const ext = bookInfo.book.ext;
+                let fileFormat = `${ext}+zip`;
+                let downHref = bookInfo.link;
+
+                if (ext === 'mobi') {
+                    fileFormat = 'x-mobipocket-ebook';
+                } else if (ext == 'epub') {
+                    //
+                } else {
+                    downHref = `${bookInfo.link}/zip`;
+                }
 
                 //entry
                 const e = this.makeEntry({
@@ -183,7 +195,7 @@ class BookPage extends BasePage {
                 }
 
                 //links
-                e.link = [ this.downLink({href: bookInfo.link, type: `application/${fileFormat}`}) ];
+                e.link = [ this.downLink({href: downHref, type: `application/${fileFormat}`}) ];
                 if (bookInfo.cover) {
                     let coverType = 'image/jpeg';
                     if (path.extname(bookInfo.cover) == '.png')

+ 8 - 6
server/core/utils.js

@@ -109,9 +109,10 @@ function gzipFile(inputFile, outputFile, level = 1) {
             .pipe(gzip).on('error', reject)
             .pipe(output).on('error', reject)
             .on('finish', (err) => {
-            if (err) reject(err);
-            else resolve();
-        });
+                if (err) reject(err);
+                else resolve();
+            }
+        );
     });
 }
 
@@ -125,9 +126,10 @@ function gunzipFile(inputFile, outputFile) {
             .pipe(gzip).on('error', reject)
             .pipe(output).on('error', reject)
             .on('finish', (err) => {
-            if (err) reject(err);
-            else resolve();
-        });
+                if (err) reject(err);
+                else resolve();
+            }
+        );
     });
 }
 

+ 79 - 47
server/static.js

@@ -1,5 +1,6 @@
 const fs = require('fs-extra');
 const path = require('path');
+const JSZip = require('jszip');
 
 const express = require('express');
 const utils = require('./core/utils');
@@ -7,68 +8,95 @@ const webAppDir = require('../build/appdir');
 
 const log = new (require('./core/AppLogger'))().log;//singleton
 
+function generateZip(zipFile, dataFile, data) {
+    return new Promise((resolve, reject) => {
+        const zip = new JSZip();
+        zip.file(dataFile, data)
+            .generateNodeStream({
+                streamFiles: true,
+                compression: 'DEFLATE',
+                compressionOptions: {level: 6},
+            }).on('error', reject)
+            .pipe(fs.createWriteStream(zipFile)).on('error', reject)
+            .on('finish', (err) => {
+                if (err) reject(err);
+                else resolve();
+            }
+        );
+    });
+}
+
 module.exports = (app, config) => {
     /*
     config.bookPathStatic = `${config.rootPathStatic}/book`;
     config.bookDir = `${config.publicFilesDir}/book`;
     */
     //загрузка или восстановление файлов в /public-files, при необходимости
-    app.use(config.bookPathStatic, async(req, res, next) => {
+    app.use([`${config.bookPathStatic}/:fileName/:fileType`, `${config.bookPathStatic}/:fileName`], async(req, res, next) => {
         if (req.method !== 'GET' && req.method !== 'HEAD') {
             return next();
         }
 
-        if (path.extname(req.path) == '') {
-            const bookFile = `${config.bookDir}${req.path}`;
-            const bookFileDesc = `${bookFile}.d.json`;
+        try {
+            const fileName = req.params.fileName;
+            const fileType = req.params.fileType;
 
-            let downFileName = '';
-            //восстановим из json-файла описания
-            try {
+            if (path.extname(fileName) === '') {//восстановление файлов {hash}.raw, {hash}.zip
+                let bookFile = `${config.bookDir}/${fileName}`;
+                const bookFileDesc = `${bookFile}.d.json`;
+
+                //восстановим из json-файла описания
                 if (await fs.pathExists(bookFile) && await fs.pathExists(bookFileDesc)) {
                     await utils.touchFile(bookFile);
                     await utils.touchFile(bookFileDesc);
 
                     let desc = await fs.readFile(bookFileDesc, 'utf8');
-                    desc = JSON.parse(desc);
-                    downFileName = desc.downFileName;
+                    let downFileName = (JSON.parse(desc)).downFileName;
+                    let gzipped = true;
+
+                    if (!req.acceptsEncodings('gzip') || fileType) {
+                        const rawFile = `${bookFile}.raw`;
+                        //не принимает gzip, тогда распакуем
+                        if (!await fs.pathExists(rawFile))
+                            await utils.gunzipFile(bookFile, rawFile);
+
+                        gzipped = false;
+
+                        if (fileType === undefined || fileType === 'raw') {
+                            bookFile = rawFile;
+                        } else if (fileType === 'zip') {
+                            //создаем zip-файл
+                            bookFile += '.zip';
+                            if (!await fs.pathExists(bookFile)) {
+                                const data = await fs.readFile(rawFile);
+                                await generateZip(bookFile, downFileName, data);
+                            }
+                            downFileName += '.zip';
+                        } else {
+                            throw new Error(`Unsupported file type: ${fileType}`);
+                        }
+                    }
+
+                    //отдача файла
+                    if (gzipped)
+                        res.set('Content-Encoding', 'gzip');
+                    res.set('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(downFileName)}`);
+                    res.sendFile(bookFile);
+                    return;
                 } else {
                     await fs.remove(bookFile);
                     await fs.remove(bookFileDesc);
                 }
-            } catch(e) {
-                log(LM_ERR, e.message);
-            }
-
-            if (downFileName) {
-                res.downFileName = downFileName;
-
-                if (!req.acceptsEncodings('gzip')) {
-                    //не принимает gzip, тогда распакуем
-                    const rawFile = `${bookFile}.raw`;
-                    if (!await fs.pathExists(rawFile))
-                        await utils.gunzipFile(bookFile, rawFile);
-
-                    req.url += '.raw';
-                    res.rawFile = true;
-                }
             }
+        } catch(e) {
+            log(LM_ERR, e.message);
         }
 
         return next();
     });
 
-    //заголовки при отдаче
-    app.use(config.bookPathStatic, express.static(config.bookDir, {
-        setHeaders: (res) => {
-            if (res.downFileName) {
-                if (!res.rawFile)
-                    res.set('Content-Encoding', 'gzip');
-
-                res.set('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(res.downFileName)}`);
-            }
-        },
-    }));
+    //иначе просто отдаем запрошенный файл из /public-files
+    app.use(config.bookPathStatic, express.static(config.bookDir));
 
     if (config.rootPathStatic) {
         //подмена rootPath в файлах статики WebApp при необходимости
@@ -77,18 +105,22 @@ module.exports = (app, config) => {
                 return next();
             }
 
-            const reqPath = (req.path == '/' ? '/index.html' : req.path);
-            const ext = path.extname(reqPath);
-            if (ext == '.html' || ext == '.js' || ext == '.css') {
-                const reqFile = `${config.publicDir}${reqPath}`;
-                const flagFile = `${reqFile}.replaced`;
-
-                if (!await fs.pathExists(flagFile) && await fs.pathExists(reqFile)) {
-                    const content = await fs.readFile(reqFile, 'utf8');
-                    const re = new RegExp(`/${webAppDir}`, 'g');
-                    await fs.writeFile(reqFile, content.replace(re, `${config.rootPathStatic}/${webAppDir}`));
-                    await fs.writeFile(flagFile, '');
+            try {
+                const reqPath = (req.path == '/' ? '/index.html' : req.path);
+                const ext = path.extname(reqPath);
+                if (ext == '.html' || ext == '.js' || ext == '.css') {
+                    const reqFile = `${config.publicDir}${reqPath}`;
+                    const flagFile = `${reqFile}.replaced`;
+
+                    if (!await fs.pathExists(flagFile) && await fs.pathExists(reqFile)) {
+                        const content = await fs.readFile(reqFile, 'utf8');
+                        const re = new RegExp(`/${webAppDir}`, 'g');
+                        await fs.writeFile(reqFile, content.replace(re, `${config.rootPathStatic}/${webAppDir}`));
+                        await fs.writeFile(flagFile, '');
+                    }
                 }
+            } catch(e) {
+                log(LM_ERR, e.message);
             }
 
             return next();