Compare commits

...

14 Commits

Author SHA1 Message Date
Book Pauk
73c3beaff1 Merge branch 'release/0.8.3-4' 2020-02-06 21:07:28 +07:00
Book Pauk
a6bdccd4ef Доработки конвертирования из буфера обмена 2020-02-06 21:04:40 +07:00
Book Pauk
8007991e7d Merge tag '0.8.3-3' into develop
0.8.3-3
2020-02-06 20:27:29 +07:00
Book Pauk
0e5d1ed1c3 Merge branch 'release/0.8.3-3' 2020-02-06 20:27:20 +07:00
Book Pauk
91dc2f4f71 Поправки логирования 2020-02-06 20:25:49 +07:00
Book Pauk
950bab3023 Добавлено декодирование имен файлов при распаковке Zip-архива в случае,
если кодировка имени не дает создать файл на диске
2020-02-06 20:20:29 +07:00
Book Pauk
29082a10e6 Рефакторинг 2020-02-06 20:13:33 +07:00
Book Pauk
65c1227d88 Удален node-stream-zip, т.к. в него внесены ручные правки 2020-02-06 20:12:01 +07:00
Book Pauk
5d121a68cf Поправки скриптов деплоя и запуска, добавлен авторестарт при падении сервера 2020-02-06 16:40:13 +07:00
Book Pauk
d28a8db4ff Добавлен альтернативный метод вычисления ширины строки в пикселях 2020-01-31 16:59:34 +07:00
Book Pauk
ab9e7d10dd Добавлен отлов ошибок при инициализации, добавлена генерация ошибки measureText 2020-01-31 16:08:37 +07:00
Book Pauk
3ff72b26b9 0.8.3 2020-01-31 14:52:49 +07:00
Book Pauk
404b87d78d Небольшие поправки 2020-01-30 16:34:05 +07:00
Book Pauk
dcb8fbdbf4 Merge tag '0.8.3-2' into develop
0.8.3-2
2020-01-29 01:03:20 +07:00
17 changed files with 1233 additions and 81 deletions

View File

@@ -70,7 +70,7 @@ class PasteTextPage extends Vue {
} }
loadBuffer() { loadBuffer() {
this.$emit('load-buffer', {buffer: `<cut-title>${this.bookTitle}</cut-title>${this.$refs.textArea.value}`}); this.$emit('load-buffer', {buffer: `<buffer><cut-title>${utils.escapeXml(this.bookTitle)}</cut-title>${this.$refs.textArea.value}</buffer>`});
this.close(); this.close();
} }

View File

@@ -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);
} }

View File

@@ -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();
})(); })();
} }
} }

View File

@@ -193,4 +193,13 @@ export function parseQuery(str) {
query[first] = [query[first], second]; query[first] = [query[first], second];
} }
return query; return query;
}
export function escapeXml(str) {
return str.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
;
} }

View File

@@ -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

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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
]; ];
} }

View File

@@ -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) {

View File

@@ -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');

View File

@@ -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);
}); });

View File

@@ -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

View File

@@ -6,6 +6,7 @@ class ConvertHtml extends ConvertBase {
check(data, opts) { check(data, opts) {
const {dataType} = opts; const {dataType} = opts;
//html?
if (dataType && (dataType.ext == 'html' || dataType.ext == 'xml')) if (dataType && (dataType.ext == 'html' || dataType.ext == 'xml'))
return {isText: false}; return {isText: false};
@@ -14,6 +15,11 @@ class ConvertHtml extends ConvertBase {
return {isText: true}; return {isText: true};
} }
//из буфера обмена?
if (data.toString().indexOf('<buffer>') == 0) {
return {isText: false};
}
return false; return false;
} }

View File

@@ -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,
} }

View File

@@ -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;

File diff suppressed because it is too large Load Diff