Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e5d1ed1c3 | ||
|
|
91dc2f4f71 | ||
|
|
950bab3023 | ||
|
|
29082a10e6 | ||
|
|
65c1227d88 | ||
|
|
5d121a68cf | ||
|
|
d28a8db4ff | ||
|
|
ab9e7d10dd | ||
|
|
3ff72b26b9 | ||
|
|
404b87d78d | ||
|
|
dcb8fbdbf4 |
@@ -47,12 +47,13 @@ class ProgressPage extends Vue {
|
|||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
|
this.text = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(state) {
|
setState(state) {
|
||||||
if (state.state) {
|
if (state.state) {
|
||||||
if (state.state == 'queue') {
|
if (state.state == 'queue') {
|
||||||
this.text = 'Номер в очереди: ' + (state.place ? state.place : '');
|
this.text = (state.place ? 'Номер в очереди: ' + state.place : '');
|
||||||
} else {
|
} else {
|
||||||
this.text = (ruMessage[state.state] ? ruMessage[state.state] : state.state);
|
this.text = (ruMessage[state.state] ? ruMessage[state.state] : state.state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
<div v-show="!clickControl && showStatusBar" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
<div v-show="!clickControl && showStatusBar" class="layout" v-html="statusBarClickable" @mousedown.prevent.stop @touchstart.stop
|
||||||
@click.prevent.stop="onStatusBarClick"></div>
|
@click.prevent.stop="onStatusBarClick"></div>
|
||||||
<!-- невидимым делать нельзя, вовремя не подгружаютя шрифты -->
|
<!-- невидимым делать нельзя, вовремя не подгружаютя шрифты -->
|
||||||
<canvas ref="offscreenCanvas" class="layout" style="width: 0px; height: 0px"></canvas>
|
<canvas ref="offscreenCanvas" class="layout" style="visibility: hidden"></canvas>
|
||||||
|
<div ref="measureWidth" style="position: absolute; visibility: hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -143,6 +144,8 @@ class TextPage extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
calcDrawProps() {
|
calcDrawProps() {
|
||||||
|
const wideLetter = 'Щ';
|
||||||
|
|
||||||
//preloaded fonts
|
//preloaded fonts
|
||||||
this.fontList = [`12px ${this.fontName}`];
|
this.fontList = [`12px ${this.fontName}`];
|
||||||
|
|
||||||
@@ -199,6 +202,22 @@ class TextPage extends Vue {
|
|||||||
this.drawHelper.lineHeight = this.lineHeight;
|
this.drawHelper.lineHeight = this.lineHeight;
|
||||||
this.drawHelper.context = this.context;
|
this.drawHelper.context = this.context;
|
||||||
|
|
||||||
|
//альтернатива context.measureText
|
||||||
|
if (!this.context.measureText(wideLetter).width) {
|
||||||
|
const ctx = this.$refs.measureWidth;
|
||||||
|
this.drawHelper.measureText = function(text, style) {
|
||||||
|
ctx.innerText = text;
|
||||||
|
ctx.style.font = this.fontByStyle(style);
|
||||||
|
return ctx.clientWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.drawHelper.measureTextFont = function(text, font) {
|
||||||
|
ctx.innerText = text;
|
||||||
|
ctx.style.font = font;
|
||||||
|
return ctx.clientWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//statusBar
|
//statusBar
|
||||||
this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
|
this.statusBarClickable = this.drawHelper.statusBarClickable(this.statusBarTop, this.statusBarHeight);
|
||||||
|
|
||||||
@@ -211,8 +230,10 @@ class TextPage extends Vue {
|
|||||||
this.parsed.wordWrap = this.wordWrap;
|
this.parsed.wordWrap = this.wordWrap;
|
||||||
this.parsed.cutEmptyParagraphs = this.cutEmptyParagraphs;
|
this.parsed.cutEmptyParagraphs = this.cutEmptyParagraphs;
|
||||||
this.parsed.addEmptyParagraphs = this.addEmptyParagraphs;
|
this.parsed.addEmptyParagraphs = this.addEmptyParagraphs;
|
||||||
let t = '';
|
let t = wideLetter;
|
||||||
while (this.drawHelper.measureText(t, {}) < this.w) t += 'Щ';
|
if (!this.drawHelper.measureText(t, {}))
|
||||||
|
throw new Error('Ошибка measureText');
|
||||||
|
while (this.drawHelper.measureText(t, {}) < this.w) t += wideLetter;
|
||||||
this.parsed.maxWordLength = t.length - 1;
|
this.parsed.maxWordLength = t.length - 1;
|
||||||
this.parsed.measureText = this.drawHelper.measureText.bind(this.drawHelper);
|
this.parsed.measureText = this.drawHelper.measureText.bind(this.drawHelper);
|
||||||
this.parsed.lineHeight = this.lineHeight;
|
this.parsed.lineHeight = this.lineHeight;
|
||||||
@@ -368,47 +389,51 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
if (this.lastBook) {
|
if (this.lastBook) {
|
||||||
(async() => {
|
(async() => {
|
||||||
//подождем ленивый парсинг
|
try {
|
||||||
this.stopLazyParse = true;
|
//подождем ленивый парсинг
|
||||||
while (this.doingLazyParse) await sleep(10);
|
this.stopLazyParse = true;
|
||||||
|
while (this.doingLazyParse) await sleep(10);
|
||||||
|
|
||||||
const isParsed = await bookManager.hasBookParsed(this.lastBook);
|
const isParsed = await bookManager.hasBookParsed(this.lastBook);
|
||||||
if (!isParsed) {
|
if (!isParsed) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.book = await bookManager.getBook(this.lastBook);
|
||||||
|
this.meta = bookManager.metaOnly(this.book);
|
||||||
|
this.fb2 = this.meta.fb2;
|
||||||
|
|
||||||
|
let authorNames = [];
|
||||||
|
if (this.fb2.author) {
|
||||||
|
authorNames = this.fb2.author.map(a => _.compact([
|
||||||
|
a.lastName,
|
||||||
|
a.firstName,
|
||||||
|
a.middleName
|
||||||
|
]).join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.title = _.compact([
|
||||||
|
authorNames.join(', '),
|
||||||
|
this.fb2.bookTitle
|
||||||
|
]).join(' - ');
|
||||||
|
|
||||||
|
this.$root.$emit('set-app-title', this.title);
|
||||||
|
|
||||||
|
this.parsed = this.book.parsed;
|
||||||
|
|
||||||
|
this.page1 = null;
|
||||||
|
this.page2 = null;
|
||||||
|
this.statusBar = null;
|
||||||
|
await this.stopTextScrolling();
|
||||||
|
|
||||||
|
await this.calcPropsAndLoadFonts();
|
||||||
|
|
||||||
|
this.refreshTime();
|
||||||
|
if (this.lazyParseEnabled)
|
||||||
|
this.lazyParsePara();
|
||||||
|
} catch (e) {
|
||||||
|
this.$alert(e.message, 'Ошибка', {type: 'error'});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.book = await bookManager.getBook(this.lastBook);
|
|
||||||
this.meta = bookManager.metaOnly(this.book);
|
|
||||||
this.fb2 = this.meta.fb2;
|
|
||||||
|
|
||||||
let authorNames = [];
|
|
||||||
if (this.fb2.author) {
|
|
||||||
authorNames = this.fb2.author.map(a => _.compact([
|
|
||||||
a.lastName,
|
|
||||||
a.firstName,
|
|
||||||
a.middleName
|
|
||||||
]).join(' '));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.title = _.compact([
|
|
||||||
authorNames.join(', '),
|
|
||||||
this.fb2.bookTitle
|
|
||||||
]).join(' - ');
|
|
||||||
|
|
||||||
this.$root.$emit('set-app-title', this.title);
|
|
||||||
|
|
||||||
this.parsed = this.book.parsed;
|
|
||||||
|
|
||||||
this.page1 = null;
|
|
||||||
this.page2 = null;
|
|
||||||
this.statusBar = null;
|
|
||||||
await this.stopTextScrolling();
|
|
||||||
|
|
||||||
this.calcPropsAndLoadFonts();
|
|
||||||
|
|
||||||
this.refreshTime();
|
|
||||||
if (this.lazyParseEnabled)
|
|
||||||
this.lazyParsePara();
|
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
npm run build:linux
|
npm run build:linux
|
||||||
sudo -u www-data cp -r ../../dist/linux/* /home/liberama
|
sudo -u www-data cp -r ../../dist/linux/* /home/liberama
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
sudo -H -u www-data sh -c "cd /var/www; /home/liberama/liberama"
|
sudo -H -u www-data bash -c "\
|
||||||
|
while true; do\
|
||||||
|
trap '' 2;\
|
||||||
|
cd /var/www;\
|
||||||
|
/home/liberama/liberama;\
|
||||||
|
trap 2;\
|
||||||
|
echo \"Restart after 5 sec. Press Ctrl+C to exit.\";\
|
||||||
|
sleep 5;\
|
||||||
|
done;"
|
||||||
|
|||||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Liberama",
|
"name": "Liberama",
|
||||||
"version": "0.8.2",
|
"version": "0.8.3",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -7423,11 +7423,6 @@
|
|||||||
"semver": "^5.3.0"
|
"semver": "^5.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-stream-zip": {
|
|
||||||
"version": "1.8.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.8.2.tgz",
|
|
||||||
"integrity": "sha512-zwP2F/R28Oqtl0gOLItk5QjJ6jEU8XO4kaUMgeqvCyXPgdCZlm8T/5qLMiNy+moJCBCiMQAaX7aVMRhT0t2vkQ=="
|
|
||||||
},
|
|
||||||
"nopt": {
|
"nopt": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
|
||||||
|
|||||||
@@ -71,7 +71,6 @@
|
|||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
"node-stream-zip": "^1.8.2",
|
|
||||||
"pako": "^1.0.10",
|
"pako": "^1.0.10",
|
||||||
"path-browserify": "^1.0.0",
|
"path-browserify": "^1.0.0",
|
||||||
"safe-buffer": "^5.2.0",
|
"safe-buffer": "^5.2.0",
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ class AppLogger {
|
|||||||
loggerParams = [
|
loggerParams = [
|
||||||
{log: 'ConsoleLog'},
|
{log: 'ConsoleLog'},
|
||||||
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.log`},
|
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.log`},
|
||||||
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.err.log`, exclude: [LM_OK, LM_INFO]},
|
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.err.log`, exclude: [LM_OK, LM_INFO, LM_TOTAL]},
|
||||||
|
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.fatal.log`, exclude: [LM_OK, LM_INFO, LM_WARN, LM_ERR, LM_TOTAL]},//LM_FATAL only
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ const zlib = require('zlib');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const unbzip2Stream = require('unbzip2-stream');
|
const unbzip2Stream = require('unbzip2-stream');
|
||||||
const tar = require('tar-fs');
|
const tar = require('tar-fs');
|
||||||
const ZipStreamer = require('./ZipStreamer');
|
const iconv = require('iconv-lite');
|
||||||
|
|
||||||
|
const ZipStreamer = require('./Zip/ZipStreamer');
|
||||||
const appLogger = new (require('./AppLogger'))();//singleton
|
const appLogger = new (require('./AppLogger'))();//singleton
|
||||||
const utils = require('./utils');
|
|
||||||
const FileDetector = require('./FileDetector');
|
const FileDetector = require('./FileDetector');
|
||||||
|
const textUtils = require('./Reader/BookConverter/textUtils');
|
||||||
|
const utils = require('./utils');
|
||||||
|
|
||||||
class FileDecompressor {
|
class FileDecompressor {
|
||||||
constructor(limitFileSize = 0) {
|
constructor(limitFileSize = 0) {
|
||||||
@@ -114,7 +116,25 @@ class FileDecompressor {
|
|||||||
|
|
||||||
async unZip(filename, outputDir) {
|
async unZip(filename, outputDir) {
|
||||||
const zip = new ZipStreamer();
|
const zip = new ZipStreamer();
|
||||||
return await zip.unpack(filename, outputDir, null, this.limitFileSize);
|
try {
|
||||||
|
return await zip.unpack(filename, outputDir, {
|
||||||
|
limitFileSize: this.limitFileSize,
|
||||||
|
limitFileCount: 1000
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
fs.emptyDir(outputDir);
|
||||||
|
return await zip.unpack(filename, outputDir, {
|
||||||
|
limitFileSize: this.limitFileSize,
|
||||||
|
limitFileCount: 1000,
|
||||||
|
decodeEntryNameCallback: (nameRaw) => {
|
||||||
|
const enc = textUtils.getEncodingLite(nameRaw);
|
||||||
|
if (enc.indexOf('ISO-8859') < 0) {
|
||||||
|
return iconv.decode(nameRaw, enc);
|
||||||
|
}
|
||||||
|
return nameRaw;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unBz2(filename, outputDir) {
|
unBz2(filename, outputDir) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const fs = require('fs-extra');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const log = new (require('../AppLogger'))().log;//singleton
|
const log = new (require('../AppLogger'))().log;//singleton
|
||||||
const ZipStreamer = require('../ZipStreamer');
|
const ZipStreamer = require('../Zip/ZipStreamer');
|
||||||
|
|
||||||
const utils = require('../utils');
|
const utils = require('../utils');
|
||||||
|
|
||||||
|
|||||||
@@ -226,12 +226,12 @@ class Logger {
|
|||||||
|
|
||||||
// catch ctrl+c event and exit normally
|
// catch ctrl+c event and exit normally
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
this.log(LM_WARN, 'Ctrl-C pressed, exiting...');
|
this.log(LM_FATAL, 'Ctrl-C pressed, exiting...');
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGTERM', () => {
|
process.on('SIGTERM', () => {
|
||||||
this.log(LM_WARN, 'Kill signal, exiting...');
|
this.log(LM_FATAL, 'Kill signal, exiting...');
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const iconv = require('iconv-lite');
|
const iconv = require('iconv-lite');
|
||||||
const chardet = require('chardet');
|
|
||||||
const he = require('he');
|
const he = require('he');
|
||||||
|
|
||||||
const LimitedQueue = require('../../LimitedQueue');
|
const LimitedQueue = require('../../LimitedQueue');
|
||||||
@@ -77,16 +76,6 @@ class ConvertBase {
|
|||||||
decode(data) {
|
decode(data) {
|
||||||
let selected = textUtils.getEncoding(data);
|
let selected = textUtils.getEncoding(data);
|
||||||
|
|
||||||
if (selected == 'ISO-8859-5') {
|
|
||||||
const charsetAll = chardet.detectAll(data.slice(0, 20000));
|
|
||||||
for (const charset of charsetAll) {
|
|
||||||
if (charset.name.indexOf('ISO-8859') < 0) {
|
|
||||||
selected = charset.name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected.toLowerCase() != 'utf-8')
|
if (selected.toLowerCase() != 'utf-8')
|
||||||
return iconv.decode(data, selected);
|
return iconv.decode(data, selected);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,4 +1,23 @@
|
|||||||
function getEncoding(buf, returnAll) {
|
const chardet = require('chardet');
|
||||||
|
|
||||||
|
function getEncoding(buf) {
|
||||||
|
let selected = getEncodingLite(buf);
|
||||||
|
|
||||||
|
if (selected == 'ISO-8859-5') {
|
||||||
|
const charsetAll = chardet.detectAll(buf.slice(0, 20000));
|
||||||
|
for (const charset of charsetAll) {
|
||||||
|
if (charset.name.indexOf('ISO-8859') < 0) {
|
||||||
|
selected = charset.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getEncodingLite(buf, returnAll) {
|
||||||
const lowerCase = 3;
|
const lowerCase = 3;
|
||||||
const upperCase = 1;
|
const upperCase = 1;
|
||||||
|
|
||||||
@@ -106,5 +125,6 @@ function checkIfText(buf) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getEncoding,
|
getEncoding,
|
||||||
|
getEncodingLite,
|
||||||
checkIfText,
|
checkIfText,
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ const fs = require('fs-extra');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const zipStream = require('zip-stream');
|
const zipStream = require('zip-stream');
|
||||||
const unzipStream = require('node-stream-zip');
|
const unzipStream = require('./node_stream_zip');
|
||||||
|
|
||||||
class ZipStreamer {
|
class ZipStreamer {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -52,9 +52,15 @@ class ZipStreamer {
|
|||||||
})().catch(reject); });
|
})().catch(reject); });
|
||||||
}
|
}
|
||||||
|
|
||||||
unpack(zipFile, outputDir, entryCallback, limitFileSize = 0) {
|
unpack(zipFile, outputDir, options, entryCallback) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
entryCallback = (entryCallback ? entryCallback : () => {});
|
entryCallback = (entryCallback ? entryCallback : () => {});
|
||||||
|
const {
|
||||||
|
limitFileSize = 0,
|
||||||
|
limitFileCount = 0,
|
||||||
|
decodeEntryNameCallback = false,
|
||||||
|
} = options;
|
||||||
|
|
||||||
const unzip = new unzipStream({file: zipFile});
|
const unzip = new unzipStream({file: zipFile});
|
||||||
|
|
||||||
unzip.on('error', reject);
|
unzip.on('error', reject);
|
||||||
@@ -67,23 +73,41 @@ class ZipStreamer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
unzip.on('ready', () => {
|
unzip.on('ready', () => {
|
||||||
if (limitFileSize) {
|
if (limitFileCount || limitFileSize || decodeEntryNameCallback) {
|
||||||
for (const entry of Object.values(unzip.entries())) {
|
const entries = Object.values(unzip.entries());
|
||||||
if (!entry.isDirectory && entry.size > limitFileSize) {
|
if (limitFileCount && entries.length > limitFileCount) {
|
||||||
|
reject('Слишком много файлов');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (limitFileSize && !entry.isDirectory && entry.size > limitFileSize) {
|
||||||
reject('Файл слишком большой');
|
reject('Файл слишком большой');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (decodeEntryNameCallback) {
|
||||||
|
entry.name = (decodeEntryNameCallback(entry.nameRaw)).toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unzip.extract(null, outputDir, (err) => {
|
unzip.extract(null, outputDir, (err) => {
|
||||||
if (err) reject(err);
|
if (err) {
|
||||||
unzip.close();
|
reject(err);
|
||||||
resolve(files);
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
unzip.close();
|
||||||
|
resolve(files);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ZipStreamer;
|
module.exports = ZipStreamer;
|
||||||
1055
server/core/Zip/node_stream_zip.js
Normal file
1055
server/core/Zip/node_stream_zip.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user