Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
580b030ee4 | ||
|
|
6a7cbc70d6 | ||
|
|
d76f60639c | ||
|
|
e2bea407ee | ||
|
|
558fed31aa | ||
|
|
f6513d40c8 | ||
|
|
259f9baa59 | ||
|
|
db5650e276 | ||
|
|
51ebbbc569 | ||
|
|
5184661652 | ||
|
|
7853a14ce6 | ||
|
|
a01e78ace9 | ||
|
|
f7eb576d0d | ||
|
|
34f1ad8fae | ||
|
|
c60f0991df | ||
|
|
d505fd0795 | ||
|
|
93cf506535 | ||
|
|
bfb37e55d4 | ||
|
|
92afc5cb33 | ||
|
|
75cb611701 | ||
|
|
2ec1dd58a5 | ||
|
|
7d59af54de | ||
|
|
2b5f47b3de | ||
|
|
16eebfb9a4 | ||
|
|
9025218671 | ||
|
|
6bccb546bb | ||
|
|
29d49046a0 | ||
|
|
717af9ffaf | ||
|
|
00060c9f43 | ||
|
|
759ff46c92 | ||
|
|
41957cdceb | ||
|
|
d418e3a1c9 | ||
|
|
f650124428 | ||
|
|
795d109c76 | ||
|
|
6868b3effc | ||
|
|
26747b7013 | ||
|
|
5198f8aa60 | ||
|
|
552da48a32 | ||
|
|
db8a688620 | ||
|
|
3088028d05 | ||
|
|
fd62ef865d | ||
|
|
ed74ed00ed | ||
|
|
741317aaaf | ||
|
|
9b6ecd4e6b | ||
|
|
7863b3358e | ||
|
|
e1be68ec3d | ||
|
|
a054186d4b | ||
|
|
2d5c549c83 | ||
|
|
9f6072dfe1 | ||
|
|
69c44fe1ab | ||
|
|
4fa7b2443e | ||
|
|
25a69592bb | ||
|
|
44e0b26990 | ||
|
|
c4496f8dc8 | ||
|
|
9e296231d9 | ||
|
|
49b3f05d65 | ||
|
|
f124b9c050 | ||
|
|
63a86f7c06 | ||
|
|
fd0f523c64 | ||
|
|
487e605520 | ||
|
|
9e169e1f4b | ||
|
|
9612e7ebcd |
@@ -24,8 +24,8 @@ async function main() {
|
||||
await fs.ensureDir(tempDownloadDir);
|
||||
|
||||
//sqlite3
|
||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.0/node-v72-linux-x64.tar.gz';
|
||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-linux-x64/node_sqlite3.node`;
|
||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-linux-x64.tar.gz';
|
||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-linux-x64/node_sqlite3.node`;
|
||||
|
||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||
|
||||
@@ -55,6 +55,6 @@ module.exports = merge(baseWpConfig, {
|
||||
filename: `${publicDir}/index.html`
|
||||
}),
|
||||
new CopyWebpackPlugin([{from: `${clientDir}/assets/*`, to: `${publicDir}/`, flatten: true}]),
|
||||
new AppCachePlugin({})
|
||||
new AppCachePlugin({exclude: ['../index.html']})
|
||||
]
|
||||
});
|
||||
|
||||
@@ -24,8 +24,8 @@ async function main() {
|
||||
await fs.ensureDir(tempDownloadDir);
|
||||
|
||||
//sqlite3
|
||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.0/node-v72-win32-x64.tar.gz';
|
||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-win32-x64/node_sqlite3.node`;
|
||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-win32-x64.tar.gz';
|
||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-win32-x64/node_sqlite3.node`;
|
||||
|
||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import axios from 'axios';
|
||||
import {Buffer} from 'safe-buffer';
|
||||
|
||||
import * as utils from '../share/utils';
|
||||
|
||||
@@ -13,11 +11,11 @@ const workerApi = axios.create({
|
||||
});
|
||||
|
||||
class Reader {
|
||||
async loadBook(url, callback) {
|
||||
async loadBook(opts, callback) {
|
||||
const refreshPause = 300;
|
||||
if (!callback) callback = () => {};
|
||||
|
||||
let response = await api.post('/load-book', {type: 'url', url});
|
||||
let response = await api.post('/load-book', opts);
|
||||
|
||||
const workerId = response.data.workerId;
|
||||
if (!workerId)
|
||||
@@ -66,12 +64,13 @@ class Reader {
|
||||
estSize = response.headers['content-length'];
|
||||
}
|
||||
|
||||
callback({state: 'loading', progress: 0});
|
||||
const options = {
|
||||
onDownloadProgress: progress => {
|
||||
while (progress.loaded > estSize) estSize *= 1.5;
|
||||
|
||||
if (callback)
|
||||
callback({state: 'loading', progress: Math.round((progress.loaded*100)/estSize)});
|
||||
callback({progress: Math.round((progress.loaded*100)/estSize)});
|
||||
}
|
||||
}
|
||||
//загрузка
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<h4>Возможности читалки:</h4>
|
||||
<ul>
|
||||
<li>загрузка любой страницы интернета</li>
|
||||
<li>работа в автономном режиме (без связи)</li>
|
||||
<li>изменение цвета фона, текста, размер и тип шрифта и прочее</li>
|
||||
<li>установка и запоминание текущей позиции и настроек в браузере и на сервере</li>
|
||||
<li>синхронизация данных (настроек и читаемых книг) между различными устройствами</li>
|
||||
@@ -13,7 +14,6 @@
|
||||
<li>поиск по тексту и копирование фрагмента</li>
|
||||
<li>запоминание недавних книг, скачивание книги из читалки в формате fb2</li>
|
||||
<li>управление кликом и с клавиатуры</li>
|
||||
<li>подключение к интернету не обязательно для чтения книги после ее загрузки</li>
|
||||
<li>регистрация не требуется</li>
|
||||
<li>поддерживаемые браузеры: Google Chrome, Mozilla Firefox последних версий</li>
|
||||
</ul>
|
||||
@@ -22,18 +22,17 @@
|
||||
на файл из онлайн-библиотеки (например, скопировав адрес ссылки или кнопки "скачать fb2").</p>
|
||||
<p>Поддерживаемые форматы: <b>fb2, fb2.zip, html, txt</b> и другие.</p>
|
||||
|
||||
<p>Для автономной загрузки читалки (без интернета):<br>
|
||||
В Google Chrome можно установить флаг <span class="clickable" @click="copyText('chrome://flags/#show-saved-copy', 'Ссылка на флаг успешно скопирована в буфер обмена. Можно открыть ее в новой вкладке.')">chrome://flags/#show-saved-copy</span>
|
||||
в значение "Primary". В этом случае на стандартной странице "нет соединения" появится кнопка для автономной загрузки сайта из кэша.<br>
|
||||
В Mozilla Firefox в автономном режиме сайт загружается из кэша автоматически. Если этого не происходит, можно установить опцию
|
||||
"Веб-разработка" -> "Работать автономно".</p>
|
||||
|
||||
<div v-show="mode == 'omnireader'">
|
||||
<p>Вы также можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
||||
<br><span class="clickable" @click="copyText('javascript:location.href=\'https://omnireader.ru/?url=\'+location.href;', 'Код для адреса закладки успешно скопирован в буфер обмена')">
|
||||
<strong>javascript:location.href='https://omnireader.ru/?url='+location.href;</strong>
|
||||
<p>Вы можете добавить в свой браузер закладку, указав в ее свойствах вместо адреса следующий код:
|
||||
<br><strong>javascript:location.href='https://omnireader.ru/?url='+location.href;</strong>
|
||||
|
||||
<span class="clickable" @click="copyText('javascript:location.href=\'https://omnireader.ru/?url=\'+location.href;', 'Код для адреса закладки успешно скопирован в буфер обмена')">
|
||||
(скопировать)
|
||||
</span>
|
||||
<br>Тогда, нажав на получившуюся кнопку на любой странице интернета, вы автоматически откроете ее в Omni Reader.
|
||||
<br>или перетащив на панель закладок следующую ссылку:
|
||||
<br><a style="margin-left: 50px" href="javascript:location.href='https://omnireader.ru/?url='+location.href;">Omni Reader</a>
|
||||
<br>Тогда, активировав получившуюся закладку на любой странице интернета, вы автоматически загрузите эту страницу в Omni Reader.
|
||||
<br>В Chrome для Android можно вызывать такую закладку по имени прямо в адресной строке браузера (имя стоит сделать попроще).
|
||||
</p>
|
||||
</div>
|
||||
<p>Связаться с разработчиком: <a href="mailto:bookpauk@gmail.com">bookpauk@gmail.com</a></p>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<li><b>Ctrl+C</b> - скопировать текст со страницы</li>
|
||||
<li><b>R</b> - принудительно обновить книгу в обход кэша</li>
|
||||
<li><b>X</b> - открыть недавние</li>
|
||||
<li><b>O</b> - автономный режим</li>
|
||||
<li><b>S</b> - открыть окно настроек</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
</div>
|
||||
<div class="space"></div>
|
||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openComments">Отзывы о читалке</span>
|
||||
<span v-if="mode == 'omnireader'" class="bottom-span clickable" @click="openOldVersion">Старая версия</span>
|
||||
</div>
|
||||
|
||||
<div class="part bottom">
|
||||
@@ -137,6 +138,10 @@ class LoaderPage extends Vue {
|
||||
window.open('http://samlib.ru/comment/b/bookpauk/bookpauk_reader', '_blank');
|
||||
}
|
||||
|
||||
openOldVersion() {
|
||||
window.open('http://old.omnireader.ru', '_blank');
|
||||
}
|
||||
|
||||
keyHook(event) {
|
||||
if (this.pasteTextActive) {
|
||||
return this.$refs.pasteTextPage.keyHook(event);
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<div class="space"></div>
|
||||
<el-tooltip v-show="showToolButton['offlineMode']" content="Автономный режим (без интернета)" :open-delay="1000" effect="light">
|
||||
<el-button ref="offlineMode" class="tool-button" :class="buttonActiveClass('offlineMode')" @click="buttonClick('offlineMode')"><i class="el-icon-connection"></i></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-show="showToolButton['recentBooks']" content="Открыть недавние" :open-delay="1000" effect="light">
|
||||
<el-button ref="recentBooks" class="tool-button" :class="buttonActiveClass('recentBooks')" @click="buttonClick('recentBooks')"><i class="el-icon-document"></i></el-button>
|
||||
</el-tooltip>
|
||||
@@ -257,6 +260,7 @@ class Reader extends Vue {
|
||||
searchActive = false;
|
||||
copyTextActive = false;
|
||||
recentBooksActive = false;
|
||||
offlineModeActive = false;
|
||||
settingsActive = false;
|
||||
helpActive = false;
|
||||
clickMapActive = false;
|
||||
@@ -352,6 +356,7 @@ class Reader extends Vue {
|
||||
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
||||
this.showMigrationDialog = settings.showMigrationDialog;
|
||||
this.showToolButton = settings.showToolButton;
|
||||
this.enableSitesFilter = settings.enableSitesFilter;
|
||||
|
||||
this.updateHeaderMinWidth();
|
||||
}
|
||||
@@ -506,27 +511,35 @@ class Reader extends Vue {
|
||||
}
|
||||
|
||||
async bookManagerEvent(eventName) {
|
||||
if (eventName == 'recent-changed') {
|
||||
if (this.recentBooksActive) {
|
||||
await this.$refs.recentBooksPage.updateTableData();
|
||||
if (eventName == 'set-recent' || eventName == 'recent-deleted') {
|
||||
const oldBook = (this.textPage ? this.textPage.lastBook : null);
|
||||
const oldPos = (this.textPage ? this.textPage.bookPos : null);
|
||||
const newBook = bookManager.mostRecentBook();
|
||||
|
||||
if (!(oldBook && newBook && oldBook.key == newBook.key)) {
|
||||
this.mostRecentBook();
|
||||
}
|
||||
|
||||
const oldBook = this.mostRecentBookReactive;
|
||||
const newBook = bookManager.mostRecentBook();
|
||||
if (oldBook && newBook) {
|
||||
if (oldBook.key != newBook.key) {
|
||||
if (oldBook.key != newBook.key || oldBook.path != newBook.path) {
|
||||
this.loadingBook = true;
|
||||
try {
|
||||
await this.loadBook(newBook);
|
||||
} finally {
|
||||
this.loadingBook = false;
|
||||
}
|
||||
} else if (oldBook.bookPos != newBook.bookPos) {
|
||||
} else if (oldPos != newBook.bookPos) {
|
||||
while (this.loadingBook) await utils.sleep(100);
|
||||
this.bookPosChanged({bookPos: newBook.bookPos});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (eventName == 'recent-changed') {
|
||||
if (this.recentBooksActive) {
|
||||
await this.$refs.recentBooksPage.updateTableData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get toolBarActive() {
|
||||
@@ -694,6 +707,11 @@ class Reader extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
offlineModeToggle() {
|
||||
this.offlineModeActive = !this.offlineModeActive;
|
||||
this.$refs.serverStorage.offlineModeActive = this.offlineModeActive;
|
||||
}
|
||||
|
||||
settingsToggle() {
|
||||
this.settingsActive = !this.settingsActive;
|
||||
if (this.settingsActive) {
|
||||
@@ -779,11 +797,14 @@ class Reader extends Vue {
|
||||
case 'copyText':
|
||||
this.copyTextToggle();
|
||||
break;
|
||||
case 'refresh':
|
||||
this.refreshBook();
|
||||
break;
|
||||
case 'recentBooks':
|
||||
this.recentBooksToggle();
|
||||
break;
|
||||
case 'refresh':
|
||||
this.refreshBook();
|
||||
case 'offlineMode':
|
||||
this.offlineModeToggle();
|
||||
break;
|
||||
case 'settings':
|
||||
this.settingsToggle();
|
||||
@@ -804,6 +825,7 @@ class Reader extends Vue {
|
||||
case 'search':
|
||||
case 'copyText':
|
||||
case 'recentBooks':
|
||||
case 'offlineMode':
|
||||
case 'settings':
|
||||
if (this[`${button}Active`])
|
||||
classResult = classActive;
|
||||
@@ -888,6 +910,7 @@ class Reader extends Vue {
|
||||
this.updateRoute();
|
||||
const textPage = this.$refs.page;
|
||||
if (textPage.showBook) {
|
||||
this.textPage = textPage;
|
||||
textPage.lastBook = last;
|
||||
textPage.bookPos = (last.bookPos !== undefined ? last.bookPos : 0);
|
||||
|
||||
@@ -912,8 +935,10 @@ class Reader extends Vue {
|
||||
url = 'http://' + url;
|
||||
|
||||
// уже просматривается сейчас
|
||||
const lastBook = (this.$refs.page ? this.$refs.page.lastBook : null);
|
||||
if (!opts.force && lastBook && lastBook.url == url && await bookManager.hasBookParsed(lastBook)) {
|
||||
const lastBook = (this.textPage ? this.textPage.lastBook : null);
|
||||
if (!opts.force && lastBook && lastBook.url == url &&
|
||||
(!opts.path || opts.path == lastBook.path) &&
|
||||
await bookManager.hasBookParsed(lastBook)) {
|
||||
this.loaderActive = false;
|
||||
return;
|
||||
}
|
||||
@@ -942,7 +967,7 @@ class Reader extends Vue {
|
||||
|
||||
if (!opts.force) {
|
||||
// пытаемся загрузить и распарсить книгу в менеджере из локального кэша
|
||||
const bookParsed = await bookManager.getBook({url}, (prog) => {
|
||||
const bookParsed = await bookManager.getBook({url, path: opts.path}, (prog) => {
|
||||
progress.setState({progress: prog});
|
||||
});
|
||||
|
||||
@@ -963,6 +988,7 @@ class Reader extends Vue {
|
||||
// иначе идем на сервер
|
||||
// пытаемся загрузить готовый файл с сервера
|
||||
if (wasOpened.path) {
|
||||
progress.setState({totalSteps: 5});
|
||||
try {
|
||||
const resp = await readerApi.loadCachedBook(wasOpened.path, (state) => {
|
||||
progress.setState(state);
|
||||
@@ -975,11 +1001,10 @@ class Reader extends Vue {
|
||||
}
|
||||
|
||||
progress.setState({totalSteps: 5});
|
||||
|
||||
// не удалось, скачиваем книгу полностью с конвертацией
|
||||
let loadCached = true;
|
||||
if (!book) {
|
||||
book = await readerApi.loadBook(url, (state) => {
|
||||
book = await readerApi.loadBook({url, enableSitesFilter: this.enableSitesFilter}, (state) => {
|
||||
progress.setState(state);
|
||||
});
|
||||
loadCached = false;
|
||||
@@ -1049,7 +1074,7 @@ class Reader extends Vue {
|
||||
let page = this.$refs.page;
|
||||
while (this.blinkCount) {
|
||||
this.showRefreshIcon = !this.showRefreshIcon;
|
||||
if (page.blinkCachedLoadMessage)
|
||||
if (page && page.blinkCachedLoadMessage)
|
||||
page.blinkCachedLoadMessage(this.showRefreshIcon);
|
||||
await utils.sleep(500);
|
||||
if (this.stopBlink)
|
||||
@@ -1059,7 +1084,7 @@ class Reader extends Vue {
|
||||
}
|
||||
this.showRefreshIcon = true;
|
||||
this.inBlink = false;
|
||||
if (page.blinkCachedLoadMessage)
|
||||
if (page && page.blinkCachedLoadMessage)
|
||||
page.blinkCachedLoadMessage('finish');
|
||||
});
|
||||
}
|
||||
@@ -1129,6 +1154,9 @@ class Reader extends Vue {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case 'KeyO':
|
||||
this.offlineModeToggle();
|
||||
break;
|
||||
case 'KeyS':
|
||||
this.settingsToggle();
|
||||
break;
|
||||
|
||||
@@ -251,6 +251,7 @@ class RecentBooksPage extends Vue {
|
||||
}
|
||||
if (this.tableData.length > result.length)
|
||||
this.tableData.splice(result.length);*/
|
||||
|
||||
this.tableData = result;
|
||||
this.updating = false;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ import readerApi from '../../../api/reader';
|
||||
import * as utils from '../../../share/utils';
|
||||
import * as cryptoUtils from '../../../share/cryptoUtils';
|
||||
|
||||
import localForage from 'localforage';
|
||||
const ssCacheStore = localForage.createInstance({
|
||||
name: 'ssCacheStore'
|
||||
});
|
||||
|
||||
export default @Component({
|
||||
watch: {
|
||||
serverSyncEnabled: function() {
|
||||
@@ -58,21 +63,54 @@ class ServerStorage extends Vue {
|
||||
|
||||
async init() {
|
||||
try {
|
||||
this.cachedRecent = await ssCacheStore.getItem('recent');
|
||||
if (!this.cachedRecent)
|
||||
await this.setCachedRecent({rev: 0, data: {}});
|
||||
|
||||
this.cachedRecentPatch = await ssCacheStore.getItem('recent-patch');
|
||||
if (!this.cachedRecentPatch)
|
||||
await this.setCachedRecentPatch({rev: 0, data: {}});
|
||||
|
||||
this.cachedRecentMod = await ssCacheStore.getItem('recent-mod');
|
||||
if (!this.cachedRecentMod)
|
||||
await this.setCachedRecentMod({rev: 0, data: {}});
|
||||
|
||||
if (!this.serverStorageKey) {
|
||||
//генерируем новый ключ
|
||||
await this.generateNewServerStorageKey();
|
||||
} else {
|
||||
await this.serverStorageKeyChanged();
|
||||
}
|
||||
|
||||
bookManager.addEventListener(this.bookManagerEvent);
|
||||
} finally {
|
||||
this.inited = true;
|
||||
}
|
||||
}
|
||||
|
||||
async setCachedRecent(value) {
|
||||
await ssCacheStore.setItem('recent', value);
|
||||
this.cachedRecent = value;
|
||||
}
|
||||
|
||||
async setCachedRecentPatch(value) {
|
||||
await ssCacheStore.setItem('recent-patch', value);
|
||||
this.cachedRecentPatch = value;
|
||||
}
|
||||
|
||||
async setCachedRecentMod(value) {
|
||||
await ssCacheStore.setItem('recent-mod', value);
|
||||
this.cachedRecentMod = value;
|
||||
}
|
||||
|
||||
async bookManagerEvent(eventName, itemKey) {
|
||||
if (eventName == 'recent-changed') {
|
||||
this.debouncedSaveRecent(itemKey);
|
||||
if (!this.serverSyncEnabled)
|
||||
return;
|
||||
|
||||
if (eventName == 'recent-changed') {
|
||||
if (itemKey) {
|
||||
this.debouncedSaveRecent(itemKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +141,8 @@ class ServerStorage extends Vue {
|
||||
await this.loadProfiles(force);
|
||||
this.checkCurrentProfile();
|
||||
await this.currentProfileChanged(force);
|
||||
await this.loadRecent(force);
|
||||
if (force)
|
||||
const loadSuccess = await this.loadRecent();
|
||||
if (loadSuccess && force)
|
||||
await this.saveRecent();
|
||||
}
|
||||
}
|
||||
@@ -160,12 +198,12 @@ class ServerStorage extends Vue {
|
||||
}
|
||||
|
||||
warning(message) {
|
||||
if (this.showServerStorageMessages)
|
||||
if (this.showServerStorageMessages && !this.offlineModeActive)
|
||||
this.$notify.warning({message});
|
||||
}
|
||||
|
||||
error(message) {
|
||||
if (this.showServerStorageMessages)
|
||||
if (this.showServerStorageMessages && !this.offlineModeActive)
|
||||
this.$notify.error({message});
|
||||
}
|
||||
|
||||
@@ -324,171 +362,183 @@ class ServerStorage extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async loadRecent(force = false, doNotifySuccess = true) {
|
||||
if (!this.keyInited || !this.serverSyncEnabled)
|
||||
async loadRecent(skipRevCheck = false, doNotifySuccess = true) {
|
||||
if (!this.keyInited || !this.serverSyncEnabled || this.loadingRecent)
|
||||
return;
|
||||
|
||||
const oldRecentRev = bookManager.recentRev;
|
||||
const oldRecentDiffRev = bookManager.recentDiffRev;
|
||||
//проверим ревизию на сервере
|
||||
let revs = null;
|
||||
if (!force) {
|
||||
try {
|
||||
revs = await this.storageCheck({recent: {}, recentDiff: {}});
|
||||
if (revs.state == 'success' && revs.items.recent.rev == oldRecentRev &&
|
||||
revs.items.recentDiff.rev == oldRecentDiffRev) {
|
||||
this.loadingRecent = true;
|
||||
try {
|
||||
//проверим ревизию на сервере
|
||||
let query = {recent: {}, recentPatch: {}, recentMod: {}};
|
||||
let revs = null;
|
||||
if (!skipRevCheck) {
|
||||
try {
|
||||
revs = await this.storageCheck(query);
|
||||
if (revs.state == 'success') {
|
||||
if (revs.items.recent.rev != this.cachedRecent.rev) {
|
||||
//no changes
|
||||
} else if (revs.items.recentPatch.rev != this.cachedRecentPatch.rev) {
|
||||
query = {recentPatch: {}, recentMod: {}};
|
||||
} else if (revs.items.recentMod.rev != this.cachedRecentMod.rev) {
|
||||
query = {recentMod: {}};
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
} catch(e) {
|
||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
} catch(e) {
|
||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let recent = null;
|
||||
if (force || revs.items.recent.rev != oldRecentRev || revs.items.recentDiff.rev != oldRecentDiffRev) {
|
||||
let recent = null;
|
||||
try {
|
||||
recent = await this.storageGet({recent: {}, recentDiff: {}});
|
||||
recent = await this.storageGet(query);
|
||||
} catch(e) {
|
||||
this.error(`Ошибка соединения с сервером: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (recent.state == 'success') {
|
||||
let recentDiff = recent.items.recentDiff;
|
||||
recent = recent.items.recent;
|
||||
let newRecent = recent.items.recent;
|
||||
let newRecentPatch = recent.items.recentPatch;
|
||||
let newRecentMod = recent.items.recentMod;
|
||||
|
||||
if (recent.rev == 0)
|
||||
recent.data = {};
|
||||
|
||||
this.oldRecent = _.cloneDeep(recent.data);
|
||||
let newRecent = {};
|
||||
if (recentDiff && recentDiff.data) {
|
||||
newRecent = utils.applyObjDiff(recent.data, recentDiff.data);
|
||||
this.recentDiff = _.cloneDeep(recentDiff.data);
|
||||
if (!utils.isObjDiff(this.recentDiff))
|
||||
this.recentDiff = null;
|
||||
} else {
|
||||
newRecent = recent.data;
|
||||
this.recentDiff = null;
|
||||
if (!newRecent) {
|
||||
newRecent = _.cloneDeep(this.cachedRecent);
|
||||
}
|
||||
if (!newRecentPatch) {
|
||||
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
|
||||
}
|
||||
if (!newRecentMod) {
|
||||
newRecentMod = _.cloneDeep(this.cachedRecentMod);
|
||||
}
|
||||
|
||||
if (newRecent.rev == 0) newRecent.data = {};
|
||||
if (newRecentPatch.rev == 0) newRecentPatch.data = {};
|
||||
if (newRecentMod.rev == 0) newRecentMod.data = {};
|
||||
|
||||
let result = Object.assign({}, newRecent.data, newRecentPatch.data);
|
||||
|
||||
const md = newRecentMod.data;
|
||||
if (md.key && result[md.key])
|
||||
result[md.key] = utils.applyObjDiff(result[md.key], md.mod);
|
||||
|
||||
if (newRecent.rev != this.cachedRecent.rev)
|
||||
await this.setCachedRecent(newRecent);
|
||||
if (newRecentPatch.rev != this.cachedRecentPatch.rev)
|
||||
await this.setCachedRecentPatch(newRecentPatch);
|
||||
if (newRecentMod.rev != this.cachedRecentMod.rev)
|
||||
await this.setCachedRecentMod(newRecentMod);
|
||||
|
||||
if (!bookManager.loaded) {
|
||||
this.warning('Ожидание загрузки списка книг перед синхронизацией');
|
||||
while (!bookManager.loaded) await utils.sleep(100);
|
||||
}
|
||||
await bookManager.setRecent(newRecent);
|
||||
await bookManager.setRecentRev(recent.rev);
|
||||
await bookManager.setRecentDiffRev(recentDiff.rev);
|
||||
|
||||
await bookManager.setRecent(result);
|
||||
} else {
|
||||
this.warning(`Неверный ответ сервера: ${recent.state}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (doNotifySuccess)
|
||||
this.debouncedNotifySuccess();
|
||||
if (doNotifySuccess)
|
||||
this.debouncedNotifySuccess();
|
||||
} finally {
|
||||
this.loadingRecent = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async saveRecent(itemKey) {
|
||||
async saveRecent(itemKey, recurse) {
|
||||
if (!this.keyInited || !this.serverSyncEnabled || this.savingRecent)
|
||||
return;
|
||||
|
||||
const bm = bookManager;
|
||||
|
||||
/*if (!bookManager.loaded) {
|
||||
this.warning('Функции сохранения на сервер пока недоступны');
|
||||
return;
|
||||
}*/
|
||||
let needSaveRecent = false;
|
||||
let needSaveRecentPatch = false;
|
||||
let needSaveRecentMod = false;
|
||||
|
||||
//несколько замудреная инициализация oldRecent
|
||||
if (!this.oldRecent) {
|
||||
this.oldRecent = _.cloneDeep(bookManager.recent);
|
||||
}
|
||||
let applyMod = null;
|
||||
|
||||
if (bookManager.loaded && !this.oldRecentInited) {
|
||||
this.oldRecent = _.cloneDeep(bookManager.recent);
|
||||
this.oldRecentInited = true;
|
||||
}
|
||||
//newRecentMod
|
||||
let newRecentMod = {};
|
||||
|
||||
//вычисляем дифф
|
||||
let diff = null;
|
||||
if (itemKey) {//ускоренное вычисления диффа
|
||||
let itemDiff;
|
||||
if (this.oldRecent[itemKey]) {
|
||||
itemDiff = utils.getObjDiff({[itemKey]: (this.oldRecentInited ? this.oldRecent[itemKey] : {})}, {[itemKey]: bm.recent[itemKey]});
|
||||
} else {
|
||||
itemDiff = utils.getObjDiff({}, {[itemKey]: bm.recent[itemKey]});
|
||||
if (itemKey && this.cachedRecentPatch.data[itemKey]) {
|
||||
if (this.prevItemKey == itemKey) {//сохраняем только дифф
|
||||
newRecentMod = _.cloneDeep(this.cachedRecentMod);
|
||||
newRecentMod.rev++;
|
||||
|
||||
newRecentMod.data.key = itemKey;
|
||||
newRecentMod.data.mod = utils.getObjDiff(this.cachedRecentPatch.data[itemKey], bm.recent[itemKey]);
|
||||
needSaveRecentMod = true;
|
||||
} else {//ключ не совпадает, надо сохранять патч
|
||||
applyMod = newRecentMod.data;
|
||||
}
|
||||
if (this.recentDiff) {
|
||||
diff = this.recentDiff;
|
||||
if (itemDiff.change[itemKey])
|
||||
diff.change[itemKey] = itemDiff.change[itemKey];
|
||||
if (itemDiff.add[itemKey])
|
||||
diff.add[itemKey] = itemDiff.add[itemKey];
|
||||
} else {
|
||||
diff = itemDiff;
|
||||
}
|
||||
} else {//медленное вычисление диффа
|
||||
if (this.oldRecentInited) {
|
||||
diff = utils.getObjDiff(this.oldRecent, bm.recent);
|
||||
} else
|
||||
return;
|
||||
}
|
||||
this.prevItemKey = itemKey;
|
||||
|
||||
//newRecentPatch
|
||||
let newRecentPatch = {};
|
||||
if (itemKey && !needSaveRecentMod) {
|
||||
newRecentPatch = _.cloneDeep(this.cachedRecentPatch);
|
||||
newRecentPatch.rev++;
|
||||
newRecentPatch.data[itemKey] = bm.recent[itemKey];
|
||||
if (applyMod && applyMod.key && newRecentPatch.data[applyMod.key])
|
||||
newRecentPatch.data[applyMod.key] = utils.applyObjDiff(newRecentPatch.data[applyMod.key], applyMod.mod);
|
||||
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
||||
needSaveRecentPatch = true;
|
||||
needSaveRecentMod = true;
|
||||
}
|
||||
|
||||
if (utils.isEmptyObjDiff(diff))
|
||||
return;
|
||||
|
||||
//вычисление критерия сохранения целиком
|
||||
let forceSaveRecent = JSON.stringify(diff).length > 2000;
|
||||
if (!forceSaveRecent && itemKey) {
|
||||
if (!this.sameKeyCount)
|
||||
this.sameKeyCount = 0;
|
||||
if (this.prevItemKey == itemKey)
|
||||
this.sameKeyCount++;
|
||||
|
||||
forceSaveRecent = this.sameKeyCount > 5 && (Object.keys(diff.change).length > 1);
|
||||
|
||||
this.sameKeyCount = (!forceSaveRecent ? this.sameKeyCount : 0);
|
||||
this.prevItemKey = itemKey;
|
||||
//newRecent
|
||||
let newRecent = {};
|
||||
if (!itemKey || (needSaveRecentPatch && Object.keys(newRecentPatch.data).length > 10)) {
|
||||
newRecent = {rev: this.cachedRecent.rev + 1, data: bm.recent};
|
||||
newRecentPatch = {rev: this.cachedRecentPatch.rev + 1, data: {}};
|
||||
newRecentMod = {rev: this.cachedRecentMod.rev + 1, data: {}};
|
||||
needSaveRecent = true;
|
||||
needSaveRecentPatch = true;
|
||||
needSaveRecentMod = true;
|
||||
}
|
||||
|
||||
this.recentDiff = diff;
|
||||
//query
|
||||
let query = {};
|
||||
if (needSaveRecent) {
|
||||
query = {recent: newRecent, recentPatch: newRecentPatch, recentMod: newRecentMod};
|
||||
} else if (needSaveRecentPatch) {
|
||||
query = {recentPatch: newRecentPatch, recentMod: newRecentMod};
|
||||
} else {
|
||||
query = {recentMod: newRecentMod};
|
||||
}
|
||||
|
||||
//сохранение
|
||||
this.savingRecent = true;
|
||||
try {
|
||||
if (forceSaveRecent) {//сохраняем recent целиком
|
||||
let result = {state: ''};
|
||||
let result = {state: ''};
|
||||
|
||||
try {
|
||||
result = await this.storageSet({recent: {rev: bm.recentRev + 1, data: bm.recent}, recentDiff: {rev: bm.recentDiffRev + 1, data: {}}});
|
||||
} catch(e) {
|
||||
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
|
||||
}
|
||||
try {
|
||||
result = await this.storageSet(query);
|
||||
} catch(e) {
|
||||
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
|
||||
}
|
||||
|
||||
if (result.state == 'reject') {
|
||||
await this.loadRecent(true, false);
|
||||
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||
} else if (result.state == 'success') {
|
||||
this.oldRecent = _.cloneDeep(bm.recent);
|
||||
this.recentDiff = null;
|
||||
await bm.setRecentRev(bm.recentRev + 1);
|
||||
await bm.setRecentDiffRev(bm.recentDiffRev + 1);
|
||||
}
|
||||
} else {//сохраняем только дифф
|
||||
let result = {state: ''};
|
||||
if (result.state == 'reject') {
|
||||
|
||||
try {
|
||||
result = await this.storageSet({recentDiff: {rev: bm.recentDiffRev + 1, data: this.recentDiff}});
|
||||
} catch(e) {
|
||||
this.error(`Ошибка соединения с сервером (${e.message}). Данные не сохранены и могут быть перезаписаны.`);
|
||||
}
|
||||
await this.loadRecent(false, false);
|
||||
|
||||
if (result.state == 'reject') {
|
||||
await this.loadRecent(true, false);
|
||||
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||
} else if (result.state == 'success') {
|
||||
await bm.setRecentDiffRev(bm.recentDiffRev + 1);
|
||||
this.warning(`Последние изменения отменены. Данные синхронизированы с сервером.`);
|
||||
if (!recurse && itemKey) {
|
||||
this.savingRecent = false;
|
||||
this.saveRecent(itemKey, true);
|
||||
return;
|
||||
}
|
||||
} else if (result.state == 'success') {
|
||||
if (needSaveRecent && newRecent.rev)
|
||||
await this.setCachedRecent(newRecent);
|
||||
if (needSaveRecentPatch && newRecentPatch.rev)
|
||||
await this.setCachedRecentPatch(newRecentPatch);
|
||||
if (needSaveRecentMod && newRecentMod.rev)
|
||||
await this.setCachedRecentMod(newRecentMod);
|
||||
}
|
||||
} finally {
|
||||
this.savingRecent = false;
|
||||
|
||||
@@ -304,6 +304,19 @@
|
||||
</el-col>
|
||||
<el-input-number v-model="addEmptyParagraphs" :min="0" :max="2"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<el-tooltip :open-delay="500" effect="light" placement="top">
|
||||
<template slot="content">
|
||||
Html-фильтр вырезает лишние элементы со<br>
|
||||
страницы для определенных сайтов, таких как:<br>
|
||||
samlib.ru<br>
|
||||
www.fanfiction.net<br>
|
||||
archiveofourown.org<br>
|
||||
и других
|
||||
</template>
|
||||
<el-checkbox v-model="enableSitesFilter" @change="needTextReload">Включить html-фильтр для сайтов</el-checkbox>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Изображения">
|
||||
<el-col :span="11">
|
||||
@@ -688,6 +701,10 @@ class SettingsPage extends Vue {
|
||||
this.$notify.warning({message: 'Необходимо обновить страницу (F5), чтобы изменения возымели эффект'});
|
||||
}
|
||||
|
||||
needTextReload() {
|
||||
this.$notify.warning({message: 'Необходимо обновить книгу в обход кэша, чтобы изменения возымели эффект'});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$emit('settings-toggle');
|
||||
}
|
||||
|
||||
@@ -36,9 +36,6 @@ class BookManager {
|
||||
}
|
||||
}
|
||||
|
||||
this.recentRev = await bmRecentStore.getItem('recent-rev') || 0;
|
||||
this.recentDiffRev = await bmRecentStore.getItem('recent-diff-rev') || 0;
|
||||
|
||||
this.recentChanged = true;
|
||||
|
||||
this.loadStored();//no await
|
||||
@@ -262,6 +259,11 @@ class BookManager {
|
||||
this.books[meta.key] = result;
|
||||
}
|
||||
|
||||
//Если файл на сервере изменился, считаем, что в кеше его нету
|
||||
if (meta.path && result && meta.path != result.path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result && !result.parsed) {
|
||||
let data = await bmDataStore.getItem(`bmData-${meta.key}`);
|
||||
callback(5);
|
||||
@@ -356,7 +358,8 @@ class BookManager {
|
||||
let result = this.recent[value.key];
|
||||
if (!result) {
|
||||
result = await bmRecentStore.getItem(value.key);
|
||||
this.recent[value.key] = result;
|
||||
if (result)
|
||||
this.recent[value.key] = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -369,6 +372,7 @@ class BookManager {
|
||||
this.recentLast = null;
|
||||
await bmRecentStore.setItem('recent-last', this.recentLast);
|
||||
}
|
||||
this.emit('recent-deleted', value.key);
|
||||
this.emit('recent-changed', value.key);
|
||||
}
|
||||
|
||||
@@ -410,6 +414,7 @@ class BookManager {
|
||||
|
||||
if (this.recentLast !== oldRecentLast)
|
||||
this.emit('recent-changed');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -431,6 +436,12 @@ class BookManager {
|
||||
const mergedRecent = _.cloneDeep(this.recent);
|
||||
|
||||
Object.assign(mergedRecent, value);
|
||||
|
||||
//подстраховка от hotReload
|
||||
for (let i of Object.keys(mergedRecent)) {
|
||||
if (!mergedRecent[i].key || mergedRecent[i].key !== i)
|
||||
delete mergedRecent[i];
|
||||
}
|
||||
|
||||
//"ленивое" обновление хранилища
|
||||
(async() => {
|
||||
@@ -448,20 +459,10 @@ class BookManager {
|
||||
await bmRecentStore.setItem('recent-last', this.recentLast);
|
||||
|
||||
this.recentChanged = true;
|
||||
this.emit('set-recent');
|
||||
this.emit('recent-changed');
|
||||
}
|
||||
|
||||
async setRecentRev(value) {
|
||||
await bmRecentStore.setItem('recent-rev', value);
|
||||
this.recentRev = value;
|
||||
}
|
||||
|
||||
async setRecentDiffRev(value) {
|
||||
await bmRecentStore.setItem('recent-diff-rev', value);
|
||||
this.recentDiffRev = value;
|
||||
}
|
||||
|
||||
|
||||
addEventListener(listener) {
|
||||
if (this.eventListeners.indexOf(listener) < 0)
|
||||
this.eventListeners.push(listener);
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
export const versionHistory = [
|
||||
{
|
||||
showUntil: '2019-10-17',
|
||||
header: '0.7.3 (2019-10-18)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>внутренние переделки механизма синхронизации с сервером</li>
|
||||
<li>добавлен html-фильтр для сайтов www.fanfiction.net, archiveofourown.org</li>
|
||||
<li>добавлен параметр "Включить html-фильтр для сайтов" в раздел "Вид"->"Текст" в настройках</li>
|
||||
<li>исправления багов</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-09-19',
|
||||
header: '0.7.1 (2019-09-20)',
|
||||
content:
|
||||
`
|
||||
<ul>
|
||||
<li>исправления багов</li>
|
||||
<li>на панель управления добавлена кнопка "Автономный режим"</li>
|
||||
<li>актуализирована справка</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
|
||||
{
|
||||
showUntil: '2019-10-01',
|
||||
header: '0.7.0 (2019-09-07)',
|
||||
|
||||
@@ -8,6 +8,7 @@ const toolButtons = [
|
||||
{name: 'search', show: true, text: 'Найти в тексте'},
|
||||
{name: 'copyText', show: false, text: 'Скопировать текст со страницы'},
|
||||
{name: 'refresh', show: true, text: 'Принудительно обновить книгу'},
|
||||
{name: 'offlineMode', show: false, text: 'Автономный режим (без интернета)'},
|
||||
{name: 'recentBooks', show: true, text: 'Открыть недавние'},
|
||||
];
|
||||
|
||||
@@ -182,6 +183,7 @@ const settingDefaults = {
|
||||
showServerStorageMessages: true,
|
||||
showWhatsNewDialog: true,
|
||||
showMigrationDialog: true,
|
||||
enableSitesFilter: true,
|
||||
|
||||
fontShifts: {},
|
||||
showToolButton: {},
|
||||
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
2239
package-lock.json
generated
2239
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Liberama",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.3",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@@ -41,7 +41,7 @@
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"null-loader": "^0.1.1",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"pkg": "^4.4.0",
|
||||
"pkg": "4.3.7",
|
||||
"terser-webpack-plugin": "^1.4.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"vue-class-component": "^6.3.2",
|
||||
@@ -55,12 +55,12 @@
|
||||
"webpack-merge": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.4.13",
|
||||
"appcache-webpack-plugin": "^1.4.0",
|
||||
"axios": "^0.18.1",
|
||||
"base-x": "^3.0.6",
|
||||
"chardet": "^0.7.0",
|
||||
"compression": "^1.7.4",
|
||||
"decompress-zip": "^0.2.2",
|
||||
"element-ui": "^2.12.0",
|
||||
"express": "^4.17.1",
|
||||
"fg-loadcss": "^2.1.0",
|
||||
@@ -77,7 +77,7 @@
|
||||
"safe-buffer": "^5.2.0",
|
||||
"sjcl": "^1.0.8",
|
||||
"sql-template-strings": "^2.2.2",
|
||||
"sqlite": "^3.0.3",
|
||||
"sqlite": "3.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"unbzip2-stream": "^1.3.3",
|
||||
"vue": "^2.6.10",
|
||||
|
||||
@@ -15,7 +15,10 @@ class ReaderController extends BaseController {
|
||||
try {
|
||||
if (!request.url)
|
||||
throw new Error(`key 'url' is empty`);
|
||||
const workerId = this.readerWorker.loadBookUrl(request.url);
|
||||
const workerId = this.readerWorker.loadBookUrl({
|
||||
url: request.url,
|
||||
enableSitesFilter: (request.hasOwnProperty('enableSitesFilter') ? request.enableSitesFilter : true)
|
||||
});
|
||||
const state = workerState.getState(workerId);
|
||||
return (state ? state : {});
|
||||
} catch (e) {
|
||||
|
||||
@@ -20,6 +20,9 @@ class ConvertSamlib extends ConvertBase {
|
||||
}
|
||||
|
||||
async run(data, opts) {
|
||||
if (!opts.enableSitesFilter)
|
||||
return false;
|
||||
|
||||
const checkResult = this.check(data, opts);
|
||||
if (!checkResult)
|
||||
return false;
|
||||
|
||||
71
server/core/BookConverter/ConvertSites.js
Normal file
71
server/core/BookConverter/ConvertSites.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const URL = require('url').URL;
|
||||
|
||||
const ConvertHtml = require('./ConvertHtml');
|
||||
|
||||
const sitesFilter = {
|
||||
'www.fanfiction.net': {
|
||||
converter: 'cutter',
|
||||
begin: `<div class='storytext xcontrast_txt nocopy' id='storytext'>`,
|
||||
end: `<div style='height:5px'></div><div style='clear:both;text-align:right;'>`,
|
||||
},
|
||||
'archiveofourown.org': {
|
||||
converter: 'cutter',
|
||||
begin: `<!-- BEGIN section where work skin applies -->`,
|
||||
end: `<!-- END work skin -->`,
|
||||
}
|
||||
};
|
||||
|
||||
class ConvertSites extends ConvertHtml {
|
||||
check(data, opts) {
|
||||
const {url, dataType} = opts;
|
||||
|
||||
const parsedUrl = new URL(url);
|
||||
if (dataType && dataType.ext == 'html') {
|
||||
if (sitesFilter[parsedUrl.hostname])
|
||||
return {hostname: parsedUrl.hostname};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async run(data, opts) {
|
||||
if (!opts.enableSitesFilter)
|
||||
return false;
|
||||
|
||||
const checkResult = this.check(data, opts);
|
||||
if (!checkResult)
|
||||
return false;
|
||||
|
||||
const {hostname} = checkResult;
|
||||
|
||||
let text = this.decode(data).toString();
|
||||
|
||||
text = this[sitesFilter[hostname].converter](text, sitesFilter[hostname]);
|
||||
|
||||
if (text === false)
|
||||
return false;
|
||||
|
||||
return await super.run(Buffer.from(text), {skipCheck: true, cutTitle: true});
|
||||
}
|
||||
|
||||
getTitle(text) {
|
||||
let title = '';
|
||||
const m = text.match(/<title>([\s\S]*?)<\/title>/);
|
||||
if (m)
|
||||
title = m[1];
|
||||
|
||||
return `<title>${title.trim()}</title>`;
|
||||
}
|
||||
|
||||
cutter(text, opts) {
|
||||
const title = this.getTitle(text);
|
||||
const l = text.indexOf(opts.begin);
|
||||
const r = text.indexOf(opts.end);
|
||||
if (l < 0 || r < 0 || r <= l)
|
||||
return false;
|
||||
|
||||
return text.substring(l, r) + title;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConvertSites;
|
||||
@@ -11,6 +11,7 @@ const convertClassFactory = [
|
||||
require('./ConvertMobi'),
|
||||
require('./ConvertFb2'),
|
||||
require('./ConvertSamlib'),
|
||||
require('./ConvertSites'),
|
||||
require('./ConvertHtml'),
|
||||
];
|
||||
|
||||
@@ -24,13 +25,14 @@ class BookConverter {
|
||||
}
|
||||
}
|
||||
|
||||
async convertToFb2(inputFiles, outputFile, url, callback) {
|
||||
async convertToFb2(inputFiles, outputFile, opts, callback) {
|
||||
const selectedFileType = await this.detector.detectFile(inputFiles.selectedFile);
|
||||
const data = await fs.readFile(inputFiles.selectedFile);
|
||||
|
||||
const convertOpts = Object.assign({}, opts, {inputFiles, callback, dataType: selectedFileType});
|
||||
let result = false;
|
||||
for (const convert of this.convertFactory) {
|
||||
result = await convert.run(data, {inputFiles, url, callback, dataType: selectedFileType});
|
||||
result = await convert.run(data, convertOpts);
|
||||
if (result) {
|
||||
await fs.writeFile(outputFile, result);
|
||||
break;
|
||||
@@ -38,14 +40,14 @@ class BookConverter {
|
||||
}
|
||||
|
||||
if (!result && inputFiles.nesting) {
|
||||
result = await this.convertToFb2(inputFiles.nesting, outputFile, url, callback);
|
||||
result = await this.convertToFb2(inputFiles.nesting, outputFile, opts, callback);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
if (selectedFileType)
|
||||
throw new Error(`Этот формат файла не поддерживается: ${selectedFileType.mime}`);
|
||||
else {
|
||||
throw new Error(`Не удалось определить формат файла: ${url}`);
|
||||
throw new Error(`Не удалось определить формат файла: ${opts.url}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ function getEncoding(buf, returnAll) {
|
||||
|
||||
if (returnAll)
|
||||
return sorted;
|
||||
else if (sorted[0].c > 0)
|
||||
else if (sorted[0].c > 0 && sorted[0].c > sorted[0].totalChecked/2)
|
||||
return sorted[0].codePage;
|
||||
else
|
||||
return 'ISO-8859-5';
|
||||
|
||||
@@ -4,7 +4,7 @@ const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const unbzip2Stream = require('unbzip2-stream');
|
||||
const tar = require('tar-fs');
|
||||
const DecompressZip = require('decompress-zip');
|
||||
const AdmZip = require('adm-zip');
|
||||
|
||||
const utils = require('./utils');
|
||||
const FileDetector = require('./FileDetector');
|
||||
@@ -112,26 +112,17 @@ class FileDecompressor {
|
||||
}
|
||||
|
||||
async unZip(filename, outputDir) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
const files = [];
|
||||
const unzipper = new DecompressZip(filename);
|
||||
const zip = new AdmZip(filename);
|
||||
|
||||
unzipper.on('error', function(err) {
|
||||
reject(err);
|
||||
zip.getEntries().forEach(function(zipEntry) {
|
||||
files.push({path: zipEntry.entryName, size: zipEntry.header.size});
|
||||
});
|
||||
|
||||
unzipper.on('extract', function() {
|
||||
resolve(files);
|
||||
});
|
||||
zip.extractAllTo(outputDir, true);
|
||||
|
||||
unzipper.extract({
|
||||
path: outputDir,
|
||||
filter: function(file) {
|
||||
if (file.type == 'File')
|
||||
files.push({path: file.path, size: file.uncompressedSize});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
resolve(files);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const FileDownloader = require('./FileDownloader');
|
||||
const FileDecompressor = require('./FileDecompressor');
|
||||
const BookConverter = require('./BookConverter');
|
||||
const utils = require('./utils');
|
||||
const log = require('./getLogger').getLog();
|
||||
|
||||
let singleCleanExecute = false;
|
||||
|
||||
@@ -31,7 +32,8 @@ class ReaderWorker {
|
||||
}
|
||||
}
|
||||
|
||||
async loadBook(url, wState) {
|
||||
async loadBook(opts, wState) {
|
||||
const url = opts.url;
|
||||
let errMes = '';
|
||||
let decompDir = '';
|
||||
let downloadedFilename = '';
|
||||
@@ -76,7 +78,7 @@ class ReaderWorker {
|
||||
//конвертирование в fb2
|
||||
wState.set({state: 'convert', step: 3, progress: 0});
|
||||
convertFilename = `${this.config.tempDownloadDir}/${tempFilename2}`;
|
||||
await this.bookConverter.convertToFb2(decompFiles, convertFilename, url, progress => {
|
||||
await this.bookConverter.convertToFb2(decompFiles, convertFilename, opts, progress => {
|
||||
wState.set({progress});
|
||||
});
|
||||
|
||||
@@ -104,12 +106,12 @@ class ReaderWorker {
|
||||
}
|
||||
}
|
||||
|
||||
loadBookUrl(url) {
|
||||
loadBookUrl(opts) {
|
||||
const workerId = workerState.generateWorkerId();
|
||||
const wState = workerState.getControl(workerId);
|
||||
wState.set({state: 'start'});
|
||||
|
||||
this.loadBook(url, wState);
|
||||
this.loadBook(opts, wState);
|
||||
|
||||
return workerId;
|
||||
}
|
||||
@@ -133,6 +135,7 @@ class ReaderWorker {
|
||||
|
||||
async periodicCleanDir(dir, maxSize, timeout) {
|
||||
try {
|
||||
log(`Start clean dir: ${dir}, maxSize=${maxSize}`);
|
||||
const list = await fs.readdir(dir);
|
||||
|
||||
let size = 0;
|
||||
@@ -144,16 +147,21 @@ class ReaderWorker {
|
||||
files.push({name, stat});
|
||||
}
|
||||
}
|
||||
log(`found ${files.length} files in dir ${dir}`);
|
||||
|
||||
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
||||
|
||||
let i = 0;
|
||||
while (i < files.length && size > maxSize) {
|
||||
const file = files[i];
|
||||
log(`rm ${dir}/${file.name}`);
|
||||
await fs.remove(`${dir}/${file.name}`);
|
||||
size -= file.stat.size;
|
||||
i++;
|
||||
}
|
||||
log(`removed ${i} files`);
|
||||
} catch(e) {
|
||||
log(LM_ERR, e.message);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.periodicCleanDir(dir, maxSize, timeout);
|
||||
|
||||
@@ -20,7 +20,7 @@ function webpackDevMiddleware(app) {
|
||||
function logQueries(app) {
|
||||
app.use(function(req, res, next) {
|
||||
const start = Date.now();
|
||||
log(`${req.method} ${req.originalUrl} ${JSON.stringify(req.body).substr(0, 2000)}`);
|
||||
log(`${req.method} ${req.originalUrl} ${JSON.stringify(req.body).substr(0, 4000)}`);
|
||||
//log(`${JSON.stringify(req.headers, null, 2)}`)
|
||||
res.once('finish', () => {
|
||||
log(`${Date.now() - start}ms`);
|
||||
|
||||
Reference in New Issue
Block a user