From b3ed9ea89c0eb3d04eab848088721a36f1722d44 Mon Sep 17 00:00:00 2001 From: Book Pauk Date: Mon, 5 Dec 2022 18:06:20 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=84=D0=BE=D1=80=D0=BC=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20zip-=D1=84=D0=B0=D0=B9=D0=BB=D0=B0?= =?UTF-8?q?=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/core/opds/BookPage.js | 16 ++++- server/core/utils.js | 14 ++-- server/static.js | 124 ++++++++++++++++++++++------------- 3 files changed, 100 insertions(+), 54 deletions(-) diff --git a/server/core/opds/BookPage.js b/server/core/opds/BookPage.js index b3ad1e8..d46fef3 100644 --- a/server/core/opds/BookPage.js +++ b/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') diff --git a/server/core/utils.js b/server/core/utils.js index 7dc8991..1dd4b9f 100644 --- a/server/core/utils.js +++ b/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(); + } + ); }); } diff --git a/server/static.js b/server/static.js index c27926f..0a89a32 100644 --- a/server/static.js +++ b/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`; + 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, ''); + 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();