Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d52d4a1278 | ||
|
|
57a44c5952 | ||
|
|
a04161ac7c | ||
|
|
47e46f13c3 | ||
|
|
5535bd91c8 | ||
|
|
8747a00de6 | ||
|
|
c926b86926 | ||
|
|
010ac9aa7c | ||
|
|
4ab0c337f1 | ||
|
|
f814c42fdd | ||
|
|
02aee3e625 | ||
|
|
52a32cfdd1 | ||
|
|
6faa7b2efe | ||
|
|
f8481413c9 | ||
|
|
0951d01383 | ||
|
|
da34472a6f | ||
|
|
e89b6e3ea0 | ||
|
|
977bab4745 | ||
|
|
26c73109fe | ||
|
|
65f911ad51 | ||
|
|
f8ed5ebd6a | ||
|
|
e4cb61bebe | ||
|
|
7d5310af42 | ||
|
|
f68c610c0d | ||
|
|
ccfb6a6d73 | ||
|
|
da55996e22 | ||
|
|
ecd8400a34 | ||
|
|
03914883bc | ||
|
|
9981e1f3bd | ||
|
|
4d1df66025 | ||
|
|
a0f64e188b | ||
|
|
08407a1094 | ||
|
|
445ea3bb2e | ||
|
|
0e0aab98b1 | ||
|
|
721d5eb0c1 | ||
|
|
6d99dbc3a7 | ||
|
|
2be31f649b | ||
|
|
828ac27c03 | ||
|
|
b3d614002f | ||
|
|
2b2000ca10 | ||
|
|
8d7428d099 | ||
|
|
57f8322f31 | ||
|
|
bee7bc4294 | ||
|
|
28702065bc | ||
|
|
c248057081 | ||
|
|
6186f5e138 | ||
|
|
2201d8176d | ||
|
|
2ba6819876 | ||
|
|
a393b2a370 | ||
|
|
59fe713df2 | ||
|
|
4b8efaca9a | ||
|
|
a26100a8d0 | ||
|
|
8c52f4718c | ||
|
|
85b5c3c4ec | ||
|
|
4fd559e4c7 | ||
|
|
a337d0ddc7 | ||
|
|
9e4cb7071e | ||
|
|
c3f1707343 | ||
|
|
1ed058a553 | ||
|
|
0500a8178d | ||
|
|
7d0059f573 | ||
|
|
4e3b882362 | ||
|
|
13cf47873e | ||
|
|
7ee23ec38f | ||
|
|
eebf17c42c | ||
|
|
f84536788b |
106
LICENSE.md
Normal file
106
LICENSE.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# CC0 1.0 Universal
|
||||||
|
|
||||||
|
## Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
|
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||||
|
subsequent owner(s) (each and all, an “owner”) of an original work of
|
||||||
|
authorship and/or a database (each, a “Work”).
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||||
|
purpose of contributing to a commons of creative, cultural and scientific works
|
||||||
|
(“Commons”) that the public can reliably and without fear of later claims of
|
||||||
|
infringement build upon, modify, incorporate in other works, reuse and
|
||||||
|
redistribute as freely as possible in any form whatsoever and for any purposes,
|
||||||
|
including without limitation commercial purposes. These owners may contribute
|
||||||
|
to the Commons to promote the ideal of a free culture and the further
|
||||||
|
production of creative, cultural and scientific works, or to gain reputation or
|
||||||
|
greater distribution for their Work in part through the use and efforts of
|
||||||
|
others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any expectation of
|
||||||
|
additional consideration or compensation, the person associating CC0 with a
|
||||||
|
Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
|
||||||
|
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
|
||||||
|
publicly distribute the Work under its terms, with knowledge of his or her
|
||||||
|
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||||
|
effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights (“Copyright and
|
||||||
|
Related Rights”). Copyright and Related Rights include, but are not limited
|
||||||
|
to, the following:
|
||||||
|
1. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
2. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
3. publicity and privacy rights pertaining to a person’s image or likeness
|
||||||
|
depicted in a Work;
|
||||||
|
4. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(i), below;
|
||||||
|
5. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
6. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation thereof,
|
||||||
|
including any amended or successor version of such directive); and
|
||||||
|
7. other similar, equivalent or corresponding rights throughout the world
|
||||||
|
based on applicable law or treaty, and any national implementations
|
||||||
|
thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||||
|
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||||
|
unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
|
||||||
|
and Related Rights and associated claims and causes of action, whether now
|
||||||
|
known or unknown (including existing as well as future claims and causes of
|
||||||
|
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||||
|
duration provided by applicable law or treaty (including future time
|
||||||
|
extensions), (iii) in any current or future medium and for any number of
|
||||||
|
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||||
|
commercial, advertising or promotional purposes (the “Waiver”). Affirmer makes
|
||||||
|
the Waiver for the benefit of each member of the public at large and to the
|
||||||
|
detriment of Affirmer’s heirs and successors, fully intending that such Waiver
|
||||||
|
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||||
|
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||||
|
by the public as contemplated by Affirmer’s express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||||
|
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||||
|
shall be preserved to the maximum extent permitted taking into account
|
||||||
|
Affirmer’s express Statement of Purpose. In addition, to the extent the Waiver
|
||||||
|
is so judged Affirmer hereby grants to each affected person a royalty-free, non
|
||||||
|
transferable, non sublicensable, non exclusive, irrevocable and unconditional
|
||||||
|
license to exercise Affirmer’s Copyright and Related Rights in the Work (i) in
|
||||||
|
all territories worldwide, (ii) for the maximum duration provided by applicable
|
||||||
|
law or treaty (including future time extensions), (iii) in any current or
|
||||||
|
future medium and for any number of copies, and (iv) for any purpose
|
||||||
|
whatsoever, including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the “License”). The License shall be deemed effective as of the date
|
||||||
|
CC0 was applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder of the
|
||||||
|
License, and in such case Affirmer hereby affirms that he or she will not (i)
|
||||||
|
exercise any of his or her remaining Copyright and Related Rights in the Work
|
||||||
|
or (ii) assert any associated claims and causes of action with respect to the
|
||||||
|
Work, in either case contrary to Affirmer’s express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
1. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
2. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied, statutory
|
||||||
|
or otherwise, including without limitation warranties of title,
|
||||||
|
merchantability, fitness for a particular purpose, non infringement, or
|
||||||
|
the absence of latent or other defects, accuracy, or the present or
|
||||||
|
absence of errors, whether or not discoverable, all to the greatest
|
||||||
|
extent permissible under applicable law.
|
||||||
|
3. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person’s Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the Work.
|
||||||
|
4. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
||||||
|
|
||||||
|
For more information, please see
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/.
|
||||||
42
README.md
42
README.md
@@ -1,3 +1,43 @@
|
|||||||
# Liberama
|
# Liberama
|
||||||
|
|
||||||
Свободный обмен книгами в формате fb2
|
Браузерная онлайн-читалка книг и децентрализованная библиотека.
|
||||||
|
|
||||||
|
Читалка [OmniReader](https://omnireader.ru) является частью данного проекта, размещенной на VPS:
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## VPS
|
||||||
|
Для разворачивания читалки на чистом VPS с нуля смотрите [docs/omnireader](docs/omnireader/README.md)
|
||||||
|
|
||||||
|
## Сборка проекта
|
||||||
|
Необходима версия node.js не ниже 10.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://github.com/bookpauk/liberama
|
||||||
|
$ cd liberama
|
||||||
|
$ npm i
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
```
|
||||||
|
$ npm run build:win
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
```
|
||||||
|
$ npm run build:linux
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат сборки будет доступен в каталоге `dist/linux|win` в виде исполнимого (standalone) файла
|
||||||
|
|
||||||
|
### Разработка
|
||||||
|
```
|
||||||
|
$ npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Помочь проекту
|
||||||
|
|
||||||
|
* bitcoin: 3EbgZ7MK1UVaN38Gty5DCBtS4PknM4Ut85
|
||||||
|
* litecoin: MP39Riec4oSNB3XMjiquKoLWxbufRYNXxZ
|
||||||
|
* monero: 8BQPnvHcPSHM5gMQsmuypDgx9NNsYqwXKfDDuswEyF2Q2ewQSfd2pkK6ydH2wmMyq2JViZvy9DQ35hLMx7g72mFWNJTPtnz
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ async function main() {
|
|||||||
await fs.ensureDir(tempDownloadDir);
|
await fs.ensureDir(tempDownloadDir);
|
||||||
|
|
||||||
//sqlite3
|
//sqlite3
|
||||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-linux-x64.tar.gz';
|
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.1/node-v72-linux-x64.tar.gz';
|
||||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-linux-x64/node_sqlite3.node`;
|
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-linux-x64/node_sqlite3.node`;
|
||||||
|
|
||||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ async function main() {
|
|||||||
await fs.ensureDir(tempDownloadDir);
|
await fs.ensureDir(tempDownloadDir);
|
||||||
|
|
||||||
//sqlite3
|
//sqlite3
|
||||||
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.0.4/node-v64-win32-x64.tar.gz';
|
const sqliteRemoteUrl = 'https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v4.1.1/node-v72-win32-x64.tar.gz';
|
||||||
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v64-win32-x64/node_sqlite3.node`;
|
const sqliteDecompressedFilename = `${tempDownloadDir}/node-v72-win32-x64/node_sqlite3.node`;
|
||||||
|
|
||||||
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
if (!await fs.pathExists(sqliteDecompressedFilename)) {
|
||||||
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
// Скачиваем node_sqlite3.node для винды, т.к. pkg не включает его в сборку
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import * as utils from '../share/utils';
|
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: '/api/reader'
|
baseURL: '/api/reader'
|
||||||
});
|
});
|
||||||
@@ -11,8 +9,50 @@ const workerApi = axios.create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
class Reader {
|
class Reader {
|
||||||
|
|
||||||
|
async getStateFinish(workerId, callback) {
|
||||||
|
if (!callback) callback = () => {};
|
||||||
|
|
||||||
|
//присылается текст, состоящий из json-объектов state каждые 300ms, с разделителем splitter между ними
|
||||||
|
const splitter = '-- aod2t5hDXU32bUFyqlFE next status --';
|
||||||
|
let lastIndex = 0;
|
||||||
|
let response = await workerApi.post('/get-state-finish', {workerId}, {
|
||||||
|
onDownloadProgress: progress => {
|
||||||
|
//небольая оптимизация, вместо простого responseText.split
|
||||||
|
const xhr = progress.target;
|
||||||
|
let currIndex = xhr.responseText.length;
|
||||||
|
if (lastIndex == currIndex)
|
||||||
|
return;
|
||||||
|
const last = xhr.responseText.substring(lastIndex, currIndex);
|
||||||
|
lastIndex = currIndex;
|
||||||
|
|
||||||
|
//быстрее будет last.split
|
||||||
|
const res = last.split(splitter).pop();
|
||||||
|
if (res) {
|
||||||
|
try {
|
||||||
|
callback(JSON.parse(res));
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//берем последний state
|
||||||
|
response = response.data.split(splitter).pop();
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
try {
|
||||||
|
response = JSON.parse(response);
|
||||||
|
} catch (e) {
|
||||||
|
response = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
async loadBook(opts, callback) {
|
async loadBook(opts, callback) {
|
||||||
const refreshPause = 300;
|
|
||||||
if (!callback) callback = () => {};
|
if (!callback) callback = () => {};
|
||||||
|
|
||||||
let response = await api.post('/load-book', opts);
|
let response = await api.post('/load-book', opts);
|
||||||
@@ -22,49 +62,98 @@ class Reader {
|
|||||||
throw new Error('Неверный ответ api');
|
throw new Error('Неверный ответ api');
|
||||||
|
|
||||||
callback({totalSteps: 4});
|
callback({totalSteps: 4});
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
while (1) {// eslint-disable-line no-constant-condition
|
|
||||||
callback(response.data);
|
callback(response.data);
|
||||||
|
|
||||||
if (response.data.state == 'finish') {//воркер закончил работу, можно скачивать кешированный на сервере файл
|
response = await this.getStateFinish(workerId, callback);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
if (response.state == 'finish') {//воркер закончил работу, можно скачивать кешированный на сервере файл
|
||||||
callback({step: 4});
|
callback({step: 4});
|
||||||
const book = await this.loadCachedBook(response.data.path, callback);
|
const book = await this.loadCachedBook(response.path, callback, false, (response.size ? response.size : -1));
|
||||||
return Object.assign({}, response.data, {data: book.data});
|
return Object.assign({}, response, {data: book.data});
|
||||||
}
|
}
|
||||||
if (response.data.state == 'error') {
|
|
||||||
let errMes = response.data.error;
|
if (response.state == 'error') {
|
||||||
|
let errMes = response.error;
|
||||||
if (errMes.indexOf('getaddrinfo') >= 0 ||
|
if (errMes.indexOf('getaddrinfo') >= 0 ||
|
||||||
errMes.indexOf('ECONNRESET') >= 0 ||
|
errMes.indexOf('ECONNRESET') >= 0 ||
|
||||||
errMes.indexOf('EINVAL') >= 0 ||
|
errMes.indexOf('EINVAL') >= 0 ||
|
||||||
errMes.indexOf('404') >= 0)
|
errMes.indexOf('404') >= 0)
|
||||||
errMes = `Ресурс не найден по адресу: ${response.data.url}`;
|
errMes = `Ресурс не найден по адресу: ${response.url}`;
|
||||||
throw new Error(errMes);
|
throw new Error(errMes);
|
||||||
}
|
}
|
||||||
if (i > 0)
|
} else {
|
||||||
await utils.sleep(refreshPause);
|
throw new Error('Пустой ответ сервера');
|
||||||
|
|
||||||
i++;
|
|
||||||
if (i > 120*1000/refreshPause) {//2 мин ждем телодвижений воркера
|
|
||||||
throw new Error('Слишком долгое время ожидания');
|
|
||||||
}
|
|
||||||
//проверка воркера
|
|
||||||
const prevProgress = response.data.progress;
|
|
||||||
const prevState = response.data.state;
|
|
||||||
response = await workerApi.post('/get-state', {workerId});
|
|
||||||
i = (prevProgress != response.data.progress || prevState != response.data.state ? 1 : i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCachedBook(url, callback){
|
async checkUrl(url) {
|
||||||
const response = await axios.head(url);
|
let fileExists = false;
|
||||||
|
try {
|
||||||
|
await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
|
||||||
|
fileExists = true;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
//восстановим при необходимости файл на сервере из удаленного облака
|
||||||
|
if (!fileExists) {
|
||||||
|
let response = await api.post('/restore-cached-file', {path: url});
|
||||||
|
|
||||||
|
const workerId = response.data.workerId;
|
||||||
|
if (!workerId)
|
||||||
|
throw new Error('Неверный ответ api');
|
||||||
|
|
||||||
|
response = await this.getStateFinish(workerId);
|
||||||
|
if (response.state == 'error') {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadCachedBook(url, callback, restore = true, estSize = -1) {
|
||||||
|
if (!callback) callback = () => {};
|
||||||
|
let response = null;
|
||||||
|
|
||||||
|
callback({state: 'loading', progress: 0});
|
||||||
|
|
||||||
|
//получение размера файла
|
||||||
|
let fileExists = false;
|
||||||
|
if (estSize < 0) {
|
||||||
|
try {
|
||||||
|
response = await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
|
||||||
|
|
||||||
let estSize = 1000000;
|
|
||||||
if (response.headers['content-length']) {
|
if (response.headers['content-length']) {
|
||||||
estSize = response.headers['content-length'];
|
estSize = response.headers['content-length'];
|
||||||
}
|
}
|
||||||
|
fileExists = true;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
callback({state: 'loading', progress: 0});
|
//восстановим при необходимости файл на сервере из удаленного облака
|
||||||
|
if (restore && !fileExists) {
|
||||||
|
response = await api.post('/restore-cached-file', {path: url});
|
||||||
|
|
||||||
|
const workerId = response.data.workerId;
|
||||||
|
if (!workerId)
|
||||||
|
throw new Error('Неверный ответ api');
|
||||||
|
|
||||||
|
response = await this.getStateFinish(workerId);
|
||||||
|
if (response.state == 'error') {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.size && estSize < 0) {
|
||||||
|
estSize = response.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//получение файла
|
||||||
|
estSize = (estSize > 0 ? estSize : 1000000);
|
||||||
const options = {
|
const options = {
|
||||||
onDownloadProgress: progress => {
|
onDownloadProgress: progress => {
|
||||||
while (progress.loaded > estSize) estSize *= 1.5;
|
while (progress.loaded > estSize) estSize *= 1.5;
|
||||||
@@ -73,7 +162,7 @@ class Reader {
|
|||||||
callback({progress: Math.round((progress.loaded*100)/estSize)});
|
callback({progress: Math.round((progress.loaded*100)/estSize)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//загрузка
|
|
||||||
return await axios.get(url, options);
|
return await axios.get(url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,22 +215,6 @@ class App extends Vue {
|
|||||||
window.history.replaceState({}, '', '/');
|
window.history.replaceState({}, '', '/');
|
||||||
this.$router.replace({ path: '/reader', query: q });
|
this.$router.replace({ path: '/reader', query: q });
|
||||||
}
|
}
|
||||||
|
|
||||||
//yandex-метрика для omnireader
|
|
||||||
if (this.config.branch == 'production' && this.mode == 'omnireader' && !this.yaMetricsDone) {
|
|
||||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
|
||||||
m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
|
||||||
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");// eslint-disable-line no-unexpected-multiline
|
|
||||||
|
|
||||||
ym(52347334, "init", {// eslint-disable-line no-undef
|
|
||||||
id:52347334,
|
|
||||||
clickmap:true,
|
|
||||||
trackLinks:true,
|
|
||||||
accurateTrackBounce:true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.yaMetricsDone = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<div id="vue-github-corner">
|
||||||
|
<a :href="url" id="github-corner" target="_blank" aria-label="View source on Github" >
|
||||||
|
<svg id="github-corner-svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 250 250"
|
||||||
|
:width="size" :height="size"
|
||||||
|
:style="svgStyle" >
|
||||||
|
<path :d="svgPath1" @mouseenter="flipColor" @mouseleave="flipColor"></path>
|
||||||
|
<path :d="svgPath2" :style="gitStyle" class="octo-arm"></path>
|
||||||
|
<path :d="svgPath3" :style="gitStyle" class="octo-body"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'GithubCorner',
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: '/'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 80
|
||||||
|
},
|
||||||
|
colorScheme: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
cornerColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#625D5D'
|
||||||
|
},
|
||||||
|
gitColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'PeachPuff'
|
||||||
|
},
|
||||||
|
leftCorner: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
flipOnHover: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
svgStyle: {
|
||||||
|
fill: this.cornerColor,
|
||||||
|
right: (this.leftCorner ? 'auto' : '0'),
|
||||||
|
left: (this.leftCorner ? '0' : 'auto'),
|
||||||
|
transform: (this.leftCorner ? 'scale(-1, 1)' : 'none')
|
||||||
|
},
|
||||||
|
gitStyle: {
|
||||||
|
fill: this.gitColor
|
||||||
|
},
|
||||||
|
flipped: false,
|
||||||
|
svgPath1: 'M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z',
|
||||||
|
svgPath2: 'M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2',
|
||||||
|
svgPath3: 'M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
flipColor: function() {
|
||||||
|
if (this.flipOnHover) {
|
||||||
|
let holdSvgFill = this.svgStyle.fill
|
||||||
|
this.svgStyle.fill = this.gitStyle.fill
|
||||||
|
this.gitStyle.fill = holdSvgFill
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount: function() {
|
||||||
|
if (this.colorScheme != 'auto') {
|
||||||
|
let sch = this.colorScheme
|
||||||
|
this.gitStyle.fill = '#fff'
|
||||||
|
|
||||||
|
if (sch.toLowerCase() == 'black') {
|
||||||
|
this.svgStyle.fill = '#151513'
|
||||||
|
}
|
||||||
|
if (sch.toLowerCase() == 'green') {
|
||||||
|
this.svgStyle.fill = '#64CEAA'
|
||||||
|
}
|
||||||
|
if (sch.toLowerCase() == 'red') {
|
||||||
|
this.svgStyle.fill = '#FD6C6C'
|
||||||
|
}
|
||||||
|
if (sch.toLowerCase() == 'blue') {
|
||||||
|
this.svgStyle.fill = '#70B7FD'
|
||||||
|
}
|
||||||
|
if (sch.toLowerCase() == 'white') {
|
||||||
|
this.svgStyle.fill = '#fff'
|
||||||
|
this.gitStyle.fill = '#151513'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#github-corner .octo-arm {
|
||||||
|
transform-origin: 130px 106px
|
||||||
|
}
|
||||||
|
#github-corner:hover .octo-arm {
|
||||||
|
animation: octocat-wave 560ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes octocat-wave {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
20% { transform: rotate(-25deg); }
|
||||||
|
40% { transform: rotate(10deg); }
|
||||||
|
60% { transform: rotate(-25deg); }
|
||||||
|
80% { transform: rotate(10deg); }
|
||||||
|
100% { transform: rotate(0deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#github-corner-svg {
|
||||||
|
color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
#github-corner-svg, #github-corner-svg .octo-arm, #github-corner-svg .octo-body {
|
||||||
|
transition: fill 1s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="main" class="main">
|
<div ref="main" class="main">
|
||||||
|
<GithubCorner url="https://github.com/bookpauk/liberama" cornerColor="#1B695F"></GithubCorner>
|
||||||
<div class="part top">
|
<div class="part top">
|
||||||
<span class="greeting bold-font">{{ title }}</span>
|
<span class="greeting bold-font">{{ title }}</span>
|
||||||
<div class="space"></div>
|
<div class="space"></div>
|
||||||
@@ -54,11 +55,14 @@
|
|||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Component from 'vue-class-component';
|
import Component from 'vue-class-component';
|
||||||
|
import GithubCorner from './GithubCorner/GithubCorner.vue';
|
||||||
|
|
||||||
import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
|
import PasteTextPage from './PasteTextPage/PasteTextPage.vue';
|
||||||
import {versionHistory} from '../versionHistory';
|
import {versionHistory} from '../versionHistory';
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
components: {
|
components: {
|
||||||
|
GithubCorner,
|
||||||
PasteTextPage,
|
PasteTextPage,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -108,7 +112,7 @@ class LoaderPage extends Vue {
|
|||||||
|
|
||||||
submitUrl() {
|
submitUrl() {
|
||||||
if (this.bookUrl) {
|
if (this.bookUrl) {
|
||||||
this.$emit('load-book', {url: this.bookUrl});
|
this.$emit('load-book', {url: this.bookUrl, force: true});
|
||||||
this.bookUrl = '';
|
this.bookUrl = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,89 +90,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
|
||||||
title="Внимание!"
|
|
||||||
:visible.sync="migrationVisible1"
|
|
||||||
width="90%">
|
|
||||||
<div>
|
|
||||||
Появилась httpS-версия сайта по адресу <a href="https://omnireader.ru" target="_blank">https://omnireader.ru</a><br>
|
|
||||||
Работа по httpS-протоколу, помимо безопасности соединения, позволяет воспользоваться всеми возможностями
|
|
||||||
современных браузеров, а именно, применительно к нашему ресурсу:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>возможность автономной работы с читалкой (без доступа к интернету), кеширование сайта через appcache</li>
|
|
||||||
<li>безопасная передача на сервер данных о настройках и читаемых книгах при включенной синхронизации; все данные шифруются на стороне
|
|
||||||
браузера ключом доступа и никто (в т.ч. администратор) не имеет возможности их прочитать
|
|
||||||
<li>использование встроенных в JS функций шифрования и других</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Для того, чтобы перейти на новую версию с сохранением настроек и читаемых книг необходимо синхронизировать обе читалки:
|
|
||||||
<ul>
|
|
||||||
<li>зайти в "Настройки"->"Профили" и поставить галочку "Включить синхронизацию с сервером"</li>
|
|
||||||
<li>там же добавить профиль устройства с любым именем для синхронизации настроек<br>
|
|
||||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
|
||||||
после этого все данные будут автоматически сохранены на сервер
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li>далее нажать на кнопку "Показать ключ доступа" и кликнуть по ссылке "Ссылка для ввода ключа"<br>
|
|
||||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
|
||||||
произойдет переход на https-версию читалки и откроется окно для ввода ключа
|
|
||||||
</span><br>
|
|
||||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
|
||||||
подтвердив ввод ключа нажатием "OK", включив синхронизацию с сервером и выбрав профиль устройства, вы восстановите все ваши настройки в новой версии
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
Старая http-версия сайта будет доступна до конца 2019 года.<br>
|
|
||||||
Приносим извинения за доставленные неудобства.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span slot="footer" class="dialog-footer">
|
|
||||||
<el-button @click="migrationDialogDisable">Больше не показывать</el-button>
|
|
||||||
<el-button @click="migrationDialogRemind">Напомнить позже</el-button>
|
|
||||||
</span>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<el-dialog
|
|
||||||
title="Внимание!"
|
|
||||||
:visible.sync="migrationVisible2"
|
|
||||||
width="90%">
|
|
||||||
<div>
|
|
||||||
Информация для пользователей старой версии читалки по адресу <a href="http://omnireader.ru" target="_blank">http://omnireader.ru</a><br>
|
|
||||||
Для того, чтобы перейти на новую httpS-версию с сохранением настроек и читаемых книг необходимо синхронизировать обе читалки:
|
|
||||||
<ul>
|
|
||||||
<li>перейти на старую версию ресурса <a href="http://omnireader.ru" target="_blank">http://omnireader.ru</a></li>
|
|
||||||
<li>зайти в "Настройки"->"Профили" и поставить галочку "Включить синхронизацию с сервером"</li>
|
|
||||||
<li>там же добавить профиль устройства с любым именем для синхронизации настроек<br>
|
|
||||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
|
||||||
после этого все данные будут автоматически сохранены на сервер
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li>далее нажать на кнопку "Показать ключ доступа" и кликнуть по ссылке "Ссылка для ввода ключа"<br>
|
|
||||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
|
||||||
произойдет переход на https-версию читалки и откроется окно для ввода ключа
|
|
||||||
</span><br>
|
|
||||||
<span style="margin-left: 20px"><i style="font-size: 90%" class="el-icon-info"></i>
|
|
||||||
подтвердив ввод ключа нажатием "OK", включив синхронизацию с сервером и выбрав профиль устройства, вы восстановите все ваши настройки в новой версии
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
Старая http-версия сайта будет доступна до конца 2019 года.<br>
|
|
||||||
Приносим извинения за доставленные неудобства.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span slot="footer" class="dialog-footer">
|
|
||||||
<el-button @click="migrationDialogDisable">Больше не показывать</el-button>
|
|
||||||
<el-button @click="migrationDialogRemind">Напомнить позже</el-button>
|
|
||||||
</span>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
</el-main>
|
</el-main>
|
||||||
|
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -282,8 +200,6 @@ class Reader extends Vue {
|
|||||||
|
|
||||||
whatsNewVisible = false;
|
whatsNewVisible = false;
|
||||||
whatsNewContent = '';
|
whatsNewContent = '';
|
||||||
migrationVisible1 = false;
|
|
||||||
migrationVisible2 = false;
|
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -320,15 +236,6 @@ class Reader extends Vue {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.loadSettings();
|
this.loadSettings();
|
||||||
|
|
||||||
//TODO: убрать в будущем
|
|
||||||
if (this.showToolButton['history']) {
|
|
||||||
const newShowToolButton = Object.assign({}, this.showToolButton);
|
|
||||||
newShowToolButton['recentBooks'] = true;
|
|
||||||
delete newShowToolButton['history'];
|
|
||||||
const newSettings = Object.assign({}, this.settings, { showToolButton: newShowToolButton });
|
|
||||||
this.commit('reader/setSettings', newSettings);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -352,7 +259,6 @@ class Reader extends Vue {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
await this.showWhatsNew();
|
await this.showWhatsNew();
|
||||||
await this.showMigration();
|
|
||||||
|
|
||||||
this.updateRoute();
|
this.updateRoute();
|
||||||
})();
|
})();
|
||||||
@@ -366,7 +272,6 @@ class Reader extends Vue {
|
|||||||
this.clickControl = settings.clickControl;
|
this.clickControl = settings.clickControl;
|
||||||
this.blinkCachedLoad = settings.blinkCachedLoad;
|
this.blinkCachedLoad = settings.blinkCachedLoad;
|
||||||
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
this.showWhatsNewDialog = settings.showWhatsNewDialog;
|
||||||
this.showMigrationDialog = settings.showMigrationDialog;
|
|
||||||
this.showToolButton = settings.showToolButton;
|
this.showToolButton = settings.showToolButton;
|
||||||
this.enableSitesFilter = settings.enableSitesFilter;
|
this.enableSitesFilter = settings.enableSitesFilter;
|
||||||
|
|
||||||
@@ -432,33 +337,6 @@ class Reader extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async showMigration() {
|
|
||||||
await utils.sleep(3000);
|
|
||||||
if (!this.settingsActive &&
|
|
||||||
this.mode == 'omnireader' && this.showMigrationDialog && this.migrationRemindDate != utils.formatDate(new Date(), 'coDate')) {
|
|
||||||
if (window.location.protocol == 'http:') {
|
|
||||||
this.migrationVisible1 = true;
|
|
||||||
} else if (window.location.protocol == 'https:') {
|
|
||||||
this.migrationVisible2 = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
migrationDialogDisable() {
|
|
||||||
this.migrationVisible1 = false;
|
|
||||||
this.migrationVisible2 = false;
|
|
||||||
if (this.showMigrationDialog) {
|
|
||||||
const newSettings = Object.assign({}, this.settings, { showMigrationDialog: false });
|
|
||||||
this.commit('reader/setSettings', newSettings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
migrationDialogRemind() {
|
|
||||||
this.migrationVisible1 = false;
|
|
||||||
this.migrationVisible2 = false;
|
|
||||||
this.commit('reader/setMigrationRemindDate', utils.formatDate(new Date(), 'coDate'));
|
|
||||||
}
|
|
||||||
|
|
||||||
openVersionHistory() {
|
openVersionHistory() {
|
||||||
this.whatsNewVisible = false;
|
this.whatsNewVisible = false;
|
||||||
this.versionHistoryToggle();
|
this.versionHistoryToggle();
|
||||||
@@ -577,10 +455,6 @@ class Reader extends Vue {
|
|||||||
return this.$store.state.reader.whatsNewContentHash;
|
return this.$store.state.reader.whatsNewContentHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
get migrationRemindDate() {
|
|
||||||
return this.$store.state.reader.migrationRemindDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
addAction(pos) {
|
addAction(pos) {
|
||||||
let a = this.actionList;
|
let a = this.actionList;
|
||||||
if (!a.length || a[a.length - 1] != pos) {
|
if (!a.length || a[a.length - 1] != pos) {
|
||||||
@@ -950,7 +824,8 @@ class Reader extends Vue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = opts.url;
|
let url = encodeURI(decodeURI(opts.url));
|
||||||
|
|
||||||
if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0) &&
|
if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0) &&
|
||||||
(url.indexOf('file://') != 0))
|
(url.indexOf('file://') != 0))
|
||||||
url = 'http://' + url;
|
url = 'http://' + url;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<span v-show="loading"><i class="el-icon-loading" style="font-size: 25px"></i> <span style="position: relative; top: -4px">Список загружается</span></span>
|
<span v-show="loading"><i class="el-icon-loading" style="font-size: 25px"></i> <span style="position: relative; top: -4px">Список загружается</span></span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<a ref="download" style='display: none;'></a>
|
||||||
<el-table
|
<el-table
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
style="width: 570px"
|
style="width: 570px"
|
||||||
@@ -72,7 +73,7 @@
|
|||||||
>
|
>
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<a v-show="isUrl(scope.row.url)" :href="scope.row.url" target="_blank">Оригинал</a><br>
|
<a v-show="isUrl(scope.row.url)" :href="scope.row.url" target="_blank">Оригинал</a><br>
|
||||||
<a :href="scope.row.path" :download="getFileNameFromPath(scope.row.path)">Скачать FB2</a>
|
<a :href="scope.row.path" @click.prevent="downloadBook(scope.row.path)">Скачать FB2</a>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
@@ -104,6 +105,7 @@ import _ from 'lodash';
|
|||||||
import * as utils from '../../../share/utils';
|
import * as utils from '../../../share/utils';
|
||||||
import Window from '../../share/Window.vue';
|
import Window from '../../share/Window.vue';
|
||||||
import bookManager from '../share/bookManager';
|
import bookManager from '../share/bookManager';
|
||||||
|
import readerApi from '../../../api/reader';
|
||||||
|
|
||||||
export default @Component({
|
export default @Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -209,7 +211,7 @@ class RecentBooksPage extends Vue {
|
|||||||
a.middleName
|
a.middleName
|
||||||
]).join(' '));
|
]).join(' '));
|
||||||
author = authorNames.join(', ');
|
author = authorNames.join(', ');
|
||||||
} else {
|
} else {//TODO: убрать в будущем
|
||||||
author = _.compact([
|
author = _.compact([
|
||||||
fb2.lastName,
|
fb2.lastName,
|
||||||
fb2.firstName,
|
fb2.firstName,
|
||||||
@@ -268,8 +270,20 @@ class RecentBooksPage extends Vue {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileNameFromPath(fb2Path) {
|
async downloadBook(fb2path) {
|
||||||
return path.basename(fb2Path).substr(0, 10) + '.fb2';
|
try {
|
||||||
|
await readerApi.checkUrl(fb2path);
|
||||||
|
|
||||||
|
const d = this.$refs.download;
|
||||||
|
d.href = fb2path;
|
||||||
|
d.download = path.basename(fb2path).substr(0, 10) + '.fb2';
|
||||||
|
d.click();
|
||||||
|
} catch (e) {
|
||||||
|
let errMes = e.message;
|
||||||
|
if (errMes.indexOf('404') >= 0)
|
||||||
|
errMes = 'Файл не найден на сервере (возможно был удален как устаревший)';
|
||||||
|
this.$alert(errMes, 'Ошибка', {type: 'error'});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openOriginal(url) {
|
openOriginal(url) {
|
||||||
|
|||||||
@@ -471,18 +471,10 @@
|
|||||||
<el-checkbox v-model="showWhatsNewDialog">Показывать уведомление "Что нового"</el-checkbox>
|
<el-checkbox v-model="showWhatsNewDialog">Показывать уведомление "Что нового"</el-checkbox>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Уведомление">
|
|
||||||
<el-tooltip :open-delay="500" effect="light">
|
|
||||||
<template slot="content">
|
|
||||||
Показывать диалог о переходе на httpS-версию
|
|
||||||
</template>
|
|
||||||
<el-checkbox v-model="showMigrationDialog">Уведомлять о переходе на httpS-версию</el-checkbox>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
|
<el-form :model="form" size="mini" label-width="120px" @submit.native.prevent>
|
||||||
<div class="partHeader">Прочее</div>
|
<div class="partHeader">Другое</div>
|
||||||
|
|
||||||
<el-form-item label="Парам. в URL">
|
<el-form-item label="Парам. в URL">
|
||||||
<el-tooltip :open-delay="500" effect="light">
|
<el-tooltip :open-delay="500" effect="light">
|
||||||
|
|||||||
@@ -225,30 +225,23 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
//scrolling page
|
//scrolling page
|
||||||
const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight;
|
const pageSpace = this.scrollHeight - this.pageLineCount*this.lineHeight;
|
||||||
let y = pageSpace/2;
|
let top = pageSpace/2;
|
||||||
if (this.showStatusBar)
|
if (this.showStatusBar)
|
||||||
y += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
|
top += this.statusBarHeight*(this.statusBarTop ? 1 : 0);
|
||||||
let page1 = this.$refs.scrollBox1;
|
let page1 = this.$refs.scrollBox1.style;
|
||||||
let page2 = this.$refs.scrollBox2;
|
let page2 = this.$refs.scrollBox2.style;
|
||||||
|
|
||||||
page1.style.perspective = '3072px';
|
page1.perspective = page2.perspective = '3072px';
|
||||||
page2.style.perspective = '3072px';
|
|
||||||
|
|
||||||
page1.style.width = this.w + this.indentLR + 'px';
|
page1.width = page2.width = this.w + this.indentLR + 'px';
|
||||||
page2.style.width = this.w + this.indentLR + 'px';
|
page1.height = page2.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
|
||||||
page1.style.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
|
page1.top = page2.top = top + 'px';
|
||||||
page2.style.height = this.scrollHeight - (pageSpace > 0 ? pageSpace : 0) + 'px';
|
page1.left = page2.left = this.indentLR + 'px';
|
||||||
page1.style.top = y + 'px';
|
|
||||||
page2.style.top = y + 'px';
|
|
||||||
page1.style.left = this.indentLR + 'px';
|
|
||||||
page2.style.left = this.indentLR + 'px';
|
|
||||||
|
|
||||||
page1 = this.$refs.scrollingPage1;
|
page1 = this.$refs.scrollingPage1.style;
|
||||||
page2 = this.$refs.scrollingPage2;
|
page2 = this.$refs.scrollingPage2.style;
|
||||||
page1.style.width = this.w + this.indentLR + 'px';
|
page1.width = page2.width = this.w + this.indentLR + 'px';
|
||||||
page2.style.width = this.w + this.indentLR + 'px';
|
page1.height = page2.height = this.scrollHeight + this.lineHeight + 'px';
|
||||||
page1.style.height = this.scrollHeight + this.lineHeight + 'px';
|
|
||||||
page2.style.height = this.scrollHeight + this.lineHeight + 'px';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkLoadedFonts() {
|
async checkLoadedFonts() {
|
||||||
@@ -334,15 +327,17 @@ class TextPage extends Vue {
|
|||||||
|
|
||||||
this.draw();
|
this.draw();
|
||||||
|
|
||||||
// шрифты хрен знает когда подгружаются в div, поэтому
|
// ширина шрифта некоторое время выдается неверно, поэтому
|
||||||
|
if (!omitLoadFonts) {
|
||||||
const parsed = this.parsed;
|
const parsed = this.parsed;
|
||||||
await sleep(5000);
|
await sleep(100);
|
||||||
if (this.parsed === parsed) {
|
if (this.parsed === parsed) {
|
||||||
parsed.force = true;
|
parsed.force = true;
|
||||||
this.draw();
|
this.draw();
|
||||||
parsed.force = false;
|
parsed.force = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loadSettings() {
|
loadSettings() {
|
||||||
(async() => {
|
(async() => {
|
||||||
|
|||||||
@@ -32,9 +32,6 @@ export default class BookParser {
|
|||||||
|
|
||||||
//defaults
|
//defaults
|
||||||
let fb2 = {
|
let fb2 = {
|
||||||
firstName: '',
|
|
||||||
middleName: '',
|
|
||||||
lastName: '',
|
|
||||||
bookTitle: '',
|
bookTitle: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -319,7 +319,6 @@ class BookManager {
|
|||||||
|
|
||||||
metaOnly(book) {
|
metaOnly(book) {
|
||||||
let result = Object.assign({}, book);
|
let result = Object.assign({}, book);
|
||||||
delete result.data;//можно будет убрать эту строку со временем
|
|
||||||
delete result.parsed;
|
delete result.parsed;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,51 @@
|
|||||||
export const versionHistory = [
|
export const versionHistory = [
|
||||||
|
{
|
||||||
|
showUntil: '2020-01-19',
|
||||||
|
header: '0.8.2 (2020-01-20)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>внутренние оптимизации</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2020-01-06',
|
||||||
|
header: '0.8.1 (2020-01-07)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>добавлена частичная поддержка формата FB3</li>
|
||||||
|
<li>исправлен баг "Request path contains unescaped characters"</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2020-01-05',
|
||||||
|
header: '0.8.0 (2020-01-02)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>окончательный переход на https</li>
|
||||||
|
<li>код проекта теперь Open Source: <a href="https://github.com/bookpauk/liberama" target="_blank">https://github.com/bookpauk/liberama</a></li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
showUntil: '2019-11-26',
|
||||||
|
header: '0.7.9 (2019-11-27)',
|
||||||
|
content:
|
||||||
|
`
|
||||||
|
<ul>
|
||||||
|
<li>добавлен неубираемый баннер для http-версии о переходе на httpS</li>
|
||||||
|
<li>исправления багов</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
showUntil: '2019-11-24',
|
showUntil: '2019-11-24',
|
||||||
header: '0.7.8 (2019-11-25)',
|
header: '0.7.8 (2019-11-25)',
|
||||||
|
|||||||
@@ -182,7 +182,6 @@ const settingDefaults = {
|
|||||||
imageFitWidth: true,
|
imageFitWidth: true,
|
||||||
showServerStorageMessages: true,
|
showServerStorageMessages: true,
|
||||||
showWhatsNewDialog: true,
|
showWhatsNewDialog: true,
|
||||||
showMigrationDialog: true,
|
|
||||||
enableSitesFilter: true,
|
enableSitesFilter: true,
|
||||||
|
|
||||||
fontShifts: {},
|
fontShifts: {},
|
||||||
@@ -205,7 +204,6 @@ const state = {
|
|||||||
profilesRev: 0,
|
profilesRev: 0,
|
||||||
allowProfilesSave: false,//подстраховка для разработки
|
allowProfilesSave: false,//подстраховка для разработки
|
||||||
whatsNewContentHash: '',
|
whatsNewContentHash: '',
|
||||||
migrationRemindDate: '',
|
|
||||||
currentProfile: '',
|
currentProfile: '',
|
||||||
settings: Object.assign({}, settingDefaults),
|
settings: Object.assign({}, settingDefaults),
|
||||||
settingsRev: {},
|
settingsRev: {},
|
||||||
@@ -240,9 +238,6 @@ const mutations = {
|
|||||||
setWhatsNewContentHash(state, value) {
|
setWhatsNewContentHash(state, value) {
|
||||||
state.whatsNewContentHash = value;
|
state.whatsNewContentHash = value;
|
||||||
},
|
},
|
||||||
setMigrationRemindDate(state, value) {
|
|
||||||
state.migrationRemindDate = value;
|
|
||||||
},
|
|
||||||
setCurrentProfile(state, value) {
|
setCurrentProfile(state, value) {
|
||||||
state.currentProfile = value;
|
state.currentProfile = value;
|
||||||
},
|
},
|
||||||
|
|||||||
BIN
docs/assets/face.jpg
Normal file
BIN
docs/assets/face.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
docs/assets/reader.jpg
Normal file
BIN
docs/assets/reader.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
84
docs/omnireader/README.md
Normal file
84
docs/omnireader/README.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
## Разворачивание сервера OmniReader в Ubuntu:
|
||||||
|
|
||||||
|
### git, clone
|
||||||
|
```
|
||||||
|
sudo apt install ssh git
|
||||||
|
git clone https://github.com/bookpauk/liberama
|
||||||
|
```
|
||||||
|
|
||||||
|
### node.js
|
||||||
|
```
|
||||||
|
sudo apt install -y curl
|
||||||
|
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
|
||||||
|
sudo apt install -y nodejs
|
||||||
|
```
|
||||||
|
|
||||||
|
### install packages
|
||||||
|
```
|
||||||
|
cd liberama
|
||||||
|
npm i
|
||||||
|
```
|
||||||
|
|
||||||
|
### create public dir
|
||||||
|
```
|
||||||
|
sudo mkdir /home/liberama
|
||||||
|
sudo chown www-data.www-data /home/liberama
|
||||||
|
```
|
||||||
|
|
||||||
|
### external converter `calibre`, download from https://download.calibre-ebook.com/
|
||||||
|
```
|
||||||
|
wget "https://download.calibre-ebook.com/3.39.1/calibre-3.39.1-x86_64.txz"
|
||||||
|
sudo -u www-data mkdir -p /home/liberama/data/calibre
|
||||||
|
sudo -u www-data tar xvf calibre-3.39.1-x86_64.txz -C /home/liberama/data/calibre
|
||||||
|
```
|
||||||
|
|
||||||
|
### external converters
|
||||||
|
```
|
||||||
|
sudo apt install libreoffice
|
||||||
|
sudo apt install poppler-utils
|
||||||
|
```
|
||||||
|
|
||||||
|
### nginx, server config
|
||||||
|
Для своего домена необходимо будет подправить docs/omnireader/omnireader.
|
||||||
|
Можно также настроить сервер для HTTP, без SSL.
|
||||||
|
```
|
||||||
|
sudo apt install nginx
|
||||||
|
sudo cp docs/omnireader/omnireader /etc/nginx/sites-available/omnireader
|
||||||
|
sudo ln -s /etc/nginx/sites-available/omnireader /etc/nginx/sites-enabled/omnireader
|
||||||
|
sudo rm /etc/nginx/sites-enabled/default
|
||||||
|
sudo service nginx reload
|
||||||
|
sudo chown -R www-data.www-data /var/www
|
||||||
|
```
|
||||||
|
|
||||||
|
### certbot
|
||||||
|
Следовать инструкции установки certbot https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx
|
||||||
|
### old.omnireader
|
||||||
|
```
|
||||||
|
sudo apt install php7.2 php7.2-curl php7.2-mbstring php7.2-fpm
|
||||||
|
sudo service php7.2-fpm restart
|
||||||
|
|
||||||
|
sudo mkdir /home/oldreader
|
||||||
|
sudo chown www-data.www-data /home/oldreader
|
||||||
|
sudo -u www-data cp -r docs/omnireader/old/* /home/oldreader
|
||||||
|
```
|
||||||
|
|
||||||
|
## Деплой и запуск
|
||||||
|
```
|
||||||
|
cd docs/omnireader
|
||||||
|
./deploy.sh
|
||||||
|
./run_server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
После первого запуска будет создан конфигурационный файл `/home/liberama/data/config.json`.
|
||||||
|
Необходимо переключить приложение в режим `omnireader`, отредактировав опцию `servers`:
|
||||||
|
```
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"serverName": "1",
|
||||||
|
"mode": "omnireader",
|
||||||
|
"ip": "0.0.0.0",
|
||||||
|
"port": "44081"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
и перезапустить `run_server.sh`
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -36,26 +36,7 @@ server {
|
|||||||
listen 80;
|
listen 80;
|
||||||
server_name omnireader.ru;
|
server_name omnireader.ru;
|
||||||
|
|
||||||
client_max_body_size 50m;
|
return 301 https://$host$request_uri;
|
||||||
|
|
||||||
gzip on;
|
|
||||||
gzip_min_length 1024;
|
|
||||||
gzip_proxied expired no-cache no-store private auth;
|
|
||||||
gzip_types *;
|
|
||||||
|
|
||||||
location /api {
|
|
||||||
proxy_pass http://127.0.0.1:44081;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /tmp {
|
|
||||||
root /home/liberama/public;
|
|
||||||
add_header Content-Type text/xml;
|
|
||||||
add_header Content-Encoding gzip;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /home/liberama/public;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
sudo bash
|
|
||||||
|
|
||||||
mkdir /home/liberama
|
|
||||||
chown www-data.www-data /home/liberama
|
|
||||||
|
|
||||||
### oldreader
|
|
||||||
# ubuntu 18
|
|
||||||
apt install php7.2 php7.2-curl php7.2-mbstring php7.2-fpm
|
|
||||||
service php7.2-fpm restart
|
|
||||||
|
|
||||||
mkdir /home/oldreader
|
|
||||||
chown www-data /home/oldreader
|
|
||||||
chgrp www-data /home/oldreader
|
|
||||||
sudo -u www-data cp -r ./old/* /home/oldreader
|
|
||||||
###
|
|
||||||
|
|
||||||
### external converter
|
|
||||||
# calibre releases https://download.calibre-ebook.com/
|
|
||||||
# download, unpack to data/calibre
|
|
||||||
# 3.39.1
|
|
||||||
wget "https://download.calibre-ebook.com/3.39.1/calibre-3.39.1-x86_64.txz"
|
|
||||||
sudo -u www-data mkdir -p /home/liberama/data/calibre
|
|
||||||
sudo -u www-data tar xvf calibre-3.39.1-x86_64.txz -C /home/liberama/data/calibre
|
|
||||||
|
|
||||||
apt install libreoffice
|
|
||||||
apt install poppler-utils
|
|
||||||
###
|
|
||||||
|
|
||||||
apt install nginx
|
|
||||||
|
|
||||||
cp omnireader /etc/nginx/sites-available/omnireader
|
|
||||||
ln -s /etc/nginx/sites-available/omnireader /etc/nginx/sites-enabled/omnireader
|
|
||||||
rm /etc/nginx/sites-enabled/default
|
|
||||||
service nginx reload
|
|
||||||
|
|
||||||
chown -R www-data.www-data /var/www
|
|
||||||
|
|
||||||
exit
|
|
||||||
|
|
||||||
@@ -1 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
sudo -H -u www-data sh -c "cd /var/www; /home/liberama/liberama"
|
sudo -H -u www-data sh -c "cd /var/www; /home/liberama/liberama"
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Разворачивание среды:
|
|
||||||
|
|
||||||
# GIT REPO
|
|
||||||
sudo apt install ssh git
|
|
||||||
git clone
|
|
||||||
|
|
||||||
#nodejs
|
|
||||||
sudo apt install -y curl
|
|
||||||
curl -sL https://deb.nodesource.com/setup_10.x | sudo bash -
|
|
||||||
sudo apt install -y nodejs
|
|
||||||
npm i
|
|
||||||
|
|
||||||
939
package-lock.json
generated
939
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "Liberama",
|
"name": "Liberama",
|
||||||
"version": "0.7.8",
|
"version": "0.8.2",
|
||||||
|
"author": "Book Pauk <bookpauk@gmail.com>",
|
||||||
|
"license": "CC0-1.0",
|
||||||
|
"repository": "bookpauk/liberama",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
@@ -27,21 +30,18 @@
|
|||||||
"babel-plugin-transform-decorators-legacy": "^1.3.5",
|
"babel-plugin-transform-decorators-legacy": "^1.3.5",
|
||||||
"babel-preset-env": "^1.3.2",
|
"babel-preset-env": "^1.3.2",
|
||||||
"clean-webpack-plugin": "^1.0.1",
|
"clean-webpack-plugin": "^1.0.1",
|
||||||
"copy-webpack-plugin": "^4.6.0",
|
"copy-webpack-plugin": "^5.1.1",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"disable-output-webpack-plugin": "^1.0.1",
|
|
||||||
"element-theme-chalk": "^2.12.0",
|
"element-theme-chalk": "^2.12.0",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-plugin-html": "^5.0.5",
|
"eslint-plugin-html": "^5.0.5",
|
||||||
"eslint-plugin-node": "^8.0.0",
|
"eslint-plugin-node": "^8.0.0",
|
||||||
"eslint-plugin-vue": "^5.2.3",
|
"eslint-plugin-vue": "^5.2.3",
|
||||||
"event-hooks-webpack-plugin": "^2.1.4",
|
|
||||||
"file-loader": "^3.0.1",
|
"file-loader": "^3.0.1",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"mini-css-extract-plugin": "^0.5.0",
|
"mini-css-extract-plugin": "^0.5.0",
|
||||||
"null-loader": "^0.1.1",
|
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||||
"pkg": "4.3.7",
|
"pkg": "^4.4.2",
|
||||||
"terser-webpack-plugin": "^1.4.1",
|
"terser-webpack-plugin": "^1.4.1",
|
||||||
"url-loader": "^1.1.2",
|
"url-loader": "^1.1.2",
|
||||||
"vue-class-component": "^6.3.2",
|
"vue-class-component": "^6.3.2",
|
||||||
@@ -77,13 +77,14 @@
|
|||||||
"safe-buffer": "^5.2.0",
|
"safe-buffer": "^5.2.0",
|
||||||
"sjcl": "^1.0.8",
|
"sjcl": "^1.0.8",
|
||||||
"sql-template-strings": "^2.2.2",
|
"sql-template-strings": "^2.2.2",
|
||||||
"sqlite": "3.0.0",
|
"sqlite": "^3.0.3",
|
||||||
"tar-fs": "^2.0.0",
|
"tar-fs": "^2.0.0",
|
||||||
"unbzip2-stream": "^1.3.3",
|
"unbzip2-stream": "^1.3.3",
|
||||||
"vue": "github:paulkamer/vue#fix_palemoon_clickhandlers_dist",
|
"vue": "github:paulkamer/vue#fix_palemoon_clickhandlers_dist",
|
||||||
"vue-router": "^3.1.3",
|
"vue-router": "^3.1.3",
|
||||||
"vuex": "^3.1.1",
|
"vuex": "^3.1.1",
|
||||||
"vuex-persistedstate": "^2.5.4",
|
"vuex-persistedstate": "^2.5.4",
|
||||||
|
"webdav-fs": "^2.0.0",
|
||||||
"zip-stream": "^2.1.2"
|
"zip-stream": "^2.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,5 +45,14 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
remoteWebDavStorage: false,
|
||||||
|
/*
|
||||||
|
remoteWebDavStorage: {
|
||||||
|
url: '127.0.0.1:1900',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const propsToSave = [
|
|||||||
'useExternalBookConverter',
|
'useExternalBookConverter',
|
||||||
|
|
||||||
'servers',
|
'servers',
|
||||||
|
'remoteWebDavStorage',
|
||||||
];
|
];
|
||||||
|
|
||||||
let instance = null;
|
let instance = null;
|
||||||
@@ -41,9 +42,9 @@ class ConfigManager {
|
|||||||
process.env.NODE_ENV = this.branch;
|
process.env.NODE_ENV = this.branch;
|
||||||
|
|
||||||
this.branchConfigFile = __dirname + `/${this.branch}.js`;
|
this.branchConfigFile = __dirname + `/${this.branch}.js`;
|
||||||
await fs.access(this.branchConfigFile);
|
|
||||||
this._config = require(this.branchConfigFile);
|
this._config = require(this.branchConfigFile);
|
||||||
|
|
||||||
|
await fs.ensureDir(this._config.dataDir);
|
||||||
this._userConfigFile = `${this._config.dataDir}/config.json`;
|
this._userConfigFile = `${this._config.dataDir}/config.json`;
|
||||||
|
|
||||||
this.inited = true;
|
this.inited = true;
|
||||||
@@ -83,6 +84,7 @@ class ConfigManager {
|
|||||||
async save() {
|
async save() {
|
||||||
if (!this.inited)
|
if (!this.inited)
|
||||||
throw new Error('not inited');
|
throw new Error('not inited');
|
||||||
|
|
||||||
const dataToSave = _.pick(this._config, propsToSave);
|
const dataToSave = _.pick(this._config, propsToSave);
|
||||||
await fs.writeFile(this.userConfigFile, JSON.stringify(dataToSave, null, 4));
|
await fs.writeFile(this.userConfigFile, JSON.stringify(dataToSave, null, 4));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,24 @@ class ReaderController extends BaseController {
|
|||||||
res.status(400).send({error});
|
res.status(400).send({error});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async restoreCachedFile(req, res) {
|
||||||
|
const request = req.body;
|
||||||
|
let error = '';
|
||||||
|
try {
|
||||||
|
if (!request.path)
|
||||||
|
throw new Error(`key 'path' is empty`);
|
||||||
|
|
||||||
|
const workerId = this.readerWorker.restoreCachedFile(request.path);
|
||||||
|
const state = this.workerState.getState(workerId);
|
||||||
|
return (state ? state : {});
|
||||||
|
} catch (e) {
|
||||||
|
error = e.message;
|
||||||
|
}
|
||||||
|
//bad request
|
||||||
|
res.status(400).send({error});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ReaderController;
|
module.exports = ReaderController;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const BaseController = require('./BaseController');
|
const BaseController = require('./BaseController');
|
||||||
const WorkerState = require('../core/WorkerState');//singleton
|
const WorkerState = require('../core/WorkerState');//singleton
|
||||||
|
const utils = require('../core/utils');
|
||||||
|
|
||||||
class WorkerController extends BaseController {
|
class WorkerController extends BaseController {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
@@ -15,6 +16,7 @@ class WorkerController extends BaseController {
|
|||||||
throw new Error(`key 'workerId' is wrong`);
|
throw new Error(`key 'workerId' is wrong`);
|
||||||
|
|
||||||
const state = this.workerState.getState(request.workerId);
|
const state = this.workerState.getState(request.workerId);
|
||||||
|
|
||||||
return (state ? state : {});
|
return (state ? state : {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e.message;
|
error = e.message;
|
||||||
@@ -23,6 +25,59 @@ class WorkerController extends BaseController {
|
|||||||
res.status(400).send({error});
|
res.status(400).send({error});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getStateFinish(req, res) {
|
||||||
|
const request = req.body;
|
||||||
|
let error = '';
|
||||||
|
try {
|
||||||
|
if (!request.workerId)
|
||||||
|
throw new Error(`key 'workerId' is wrong`);
|
||||||
|
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': 'text/json; charset=utf-8',
|
||||||
|
});
|
||||||
|
|
||||||
|
const splitter = '-- aod2t5hDXU32bUFyqlFE next status --';
|
||||||
|
const refreshPause = 300;
|
||||||
|
let i = 0;
|
||||||
|
let prevProgress = -1;
|
||||||
|
let prevState = '';
|
||||||
|
let state;
|
||||||
|
while (1) {// eslint-disable-line no-constant-condition
|
||||||
|
state = this.workerState.getState(request.workerId);
|
||||||
|
if (!state) break;
|
||||||
|
|
||||||
|
res.write(splitter + JSON.stringify(state));
|
||||||
|
res.flush();
|
||||||
|
|
||||||
|
if (state.state != 'finish' && state.state != 'error')
|
||||||
|
await utils.sleep(refreshPause);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (i > 2*60*1000/refreshPause) {//2 мин ждем телодвижений воркера
|
||||||
|
res.write(splitter + JSON.stringify({state: 'error', error: 'Слишком долгое время ожидания'}));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i = (prevProgress != state.progress || prevState != state.state ? 1 : i);
|
||||||
|
prevProgress = state.progress;
|
||||||
|
prevState = state.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
res.write(splitter + JSON.stringify({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
error = e.message;
|
||||||
|
}
|
||||||
|
//bad request
|
||||||
|
res.status(400).send({error});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = WorkerController;
|
module.exports = WorkerController;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const unbzip2Stream = require('unbzip2-stream');
|
|||||||
const tar = require('tar-fs');
|
const tar = require('tar-fs');
|
||||||
const ZipStreamer = require('./ZipStreamer');
|
const ZipStreamer = require('./ZipStreamer');
|
||||||
|
|
||||||
|
const appLogger = new (require('./AppLogger'))();//singleton
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const FileDetector = require('./FileDetector');
|
const FileDetector = require('./FileDetector');
|
||||||
|
|
||||||
@@ -189,9 +190,9 @@ class FileDecompressor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async gzipFile(inputFile, outputFile) {
|
async gzipFile(inputFile, outputFile, level = 1) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const gzip = zlib.createGzip({level: 1});
|
const gzip = zlib.createGzip({level});
|
||||||
const input = fs.createReadStream(inputFile);
|
const input = fs.createReadStream(inputFile);
|
||||||
const output = fs.createWriteStream(outputFile);
|
const output = fs.createWriteStream(outputFile);
|
||||||
|
|
||||||
@@ -208,7 +209,21 @@ class FileDecompressor {
|
|||||||
const outFilename = `${outDir}/${hash}`;
|
const outFilename = `${outDir}/${hash}`;
|
||||||
|
|
||||||
if (!await fs.pathExists(outFilename)) {
|
if (!await fs.pathExists(outFilename)) {
|
||||||
await this.gzipFile(filename, outFilename);
|
await this.gzipFile(filename, outFilename, 1);
|
||||||
|
|
||||||
|
// переупакуем через некоторое время на максималках
|
||||||
|
const filenameCopy = `${filename}.copy`;
|
||||||
|
await fs.copy(filename, filenameCopy);
|
||||||
|
|
||||||
|
(async() => {
|
||||||
|
await utils.sleep(5000);
|
||||||
|
const filenameGZ = `${filename}.gz`;
|
||||||
|
await this.gzipFile(filenameCopy, filenameGZ, 9);
|
||||||
|
|
||||||
|
await fs.move(filenameGZ, outFilename, {overwrite: true});
|
||||||
|
|
||||||
|
await fs.remove(filenameCopy);
|
||||||
|
})().catch((e) => { if (appLogger.inited) appLogger.log(LM_ERR, `FileDecompressor.gzipFileIfNotExists: ${e.message}`) });
|
||||||
} else {
|
} else {
|
||||||
await utils.touchFile(outFilename);
|
await utils.touchFile(outFilename);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ const path = require('path');
|
|||||||
const ConvertBase = require('./ConvertBase');
|
const ConvertBase = require('./ConvertBase');
|
||||||
|
|
||||||
class ConvertDocX extends ConvertBase {
|
class ConvertDocX extends ConvertBase {
|
||||||
check(data, opts) {
|
async check(data, opts) {
|
||||||
const {inputFiles} = opts;
|
const {inputFiles} = opts;
|
||||||
if (this.config.useExternalBookConverter &&
|
if (this.config.useExternalBookConverter &&
|
||||||
inputFiles.sourceFileType && inputFiles.sourceFileType.ext == 'zip') {
|
inputFiles.sourceFileType && inputFiles.sourceFileType.ext == 'zip') {
|
||||||
//ищем файл '[Content_Types].xml'
|
//ищем файл '[Content_Types].xml'
|
||||||
for (const file of inputFiles.files) {
|
for (const file of inputFiles.files) {
|
||||||
if (file.path == '[Content_Types].xml') {
|
if (file.path == '[Content_Types].xml') {
|
||||||
return true;
|
const contentTypes = await fs.readFile(`${inputFiles.filesDir}/${file.path}`, 'utf8');
|
||||||
|
return contentTypes.indexOf('/word/document.xml') >= 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +31,7 @@ class ConvertDocX extends ConvertBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async run(data, opts) {
|
async run(data, opts) {
|
||||||
if (!this.check(data, opts))
|
if (!(await this.check(data, opts)))
|
||||||
return false;
|
return false;
|
||||||
await this.checkExternalConverterPresent();
|
await this.checkExternalConverterPresent();
|
||||||
|
|
||||||
|
|||||||
51
server/core/Reader/BookConverter/ConvertFb3.js
Normal file
51
server/core/Reader/BookConverter/ConvertFb3.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
|
const ConvertHtml = require('./ConvertHtml');
|
||||||
|
|
||||||
|
class ConvertDocX extends ConvertHtml {
|
||||||
|
async check(data, opts) {
|
||||||
|
const {inputFiles} = opts;
|
||||||
|
if (this.config.useExternalBookConverter &&
|
||||||
|
inputFiles.sourceFileType && inputFiles.sourceFileType.ext == 'zip') {
|
||||||
|
//ищем файл '[Content_Types].xml'
|
||||||
|
for (const file of inputFiles.files) {
|
||||||
|
if (file.path == '[Content_Types].xml') {
|
||||||
|
const contentTypes = await fs.readFile(`${inputFiles.filesDir}/${file.path}`, 'utf8');
|
||||||
|
return contentTypes.indexOf('/fb3/body.xml') >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTitle(text) {
|
||||||
|
let title = '';
|
||||||
|
const m = text.match(/<title>([\s\S]*?)<\/title>/);
|
||||||
|
if (m)
|
||||||
|
title = m[1];
|
||||||
|
|
||||||
|
return title.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(data, opts) {
|
||||||
|
if (!(await this.check(data, opts)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const {inputFiles} = opts;
|
||||||
|
|
||||||
|
let text = await fs.readFile(`${inputFiles.filesDir}/fb3/body.xml`, 'utf8');
|
||||||
|
|
||||||
|
const title = this.getTitle(text)
|
||||||
|
.replace(/<\/?p>/g, '')
|
||||||
|
;
|
||||||
|
text = `<title>${title}</title>` + text
|
||||||
|
.replace(/<title>/g, '<br><b>')
|
||||||
|
.replace(/<\/title>/g, '</b><br>')
|
||||||
|
.replace(/<subtitle>/g, '<br><br><subtitle>')
|
||||||
|
;
|
||||||
|
return await super.run(Buffer.from(text), {skipCheck: true, cutTitle: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ConvertDocX;
|
||||||
@@ -7,6 +7,7 @@ const convertClassFactory = [
|
|||||||
require('./ConvertPdf'),
|
require('./ConvertPdf'),
|
||||||
require('./ConvertRtf'),
|
require('./ConvertRtf'),
|
||||||
require('./ConvertDocX'),
|
require('./ConvertDocX'),
|
||||||
|
require('./ConvertFb3'),
|
||||||
require('./ConvertDoc'),
|
require('./ConvertDoc'),
|
||||||
require('./ConvertMobi'),
|
require('./ConvertMobi'),
|
||||||
require('./ConvertFb2'),
|
require('./ConvertFb2'),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const WorkerState = require('../WorkerState');//singleton
|
|||||||
const FileDownloader = require('../FileDownloader');
|
const FileDownloader = require('../FileDownloader');
|
||||||
const FileDecompressor = require('../FileDecompressor');
|
const FileDecompressor = require('../FileDecompressor');
|
||||||
const BookConverter = require('./BookConverter');
|
const BookConverter = require('./BookConverter');
|
||||||
|
const RemoteWebDavStorage = require('../RemoteWebDavStorage');
|
||||||
|
|
||||||
const utils = require('../utils');
|
const utils = require('../utils');
|
||||||
const log = new (require('../AppLogger'))().log;//singleton
|
const log = new (require('../AppLogger'))().log;//singleton
|
||||||
@@ -28,6 +29,11 @@ class ReaderWorker {
|
|||||||
this.decomp = new FileDecompressor();
|
this.decomp = new FileDecompressor();
|
||||||
this.bookConverter = new BookConverter(this.config);
|
this.bookConverter = new BookConverter(this.config);
|
||||||
|
|
||||||
|
this.remoteWebDavStorage = false;
|
||||||
|
if (config.remoteWebDavStorage) {
|
||||||
|
this.remoteWebDavStorage = new RemoteWebDavStorage(config.remoteWebDavStorage);
|
||||||
|
}
|
||||||
|
|
||||||
this.periodicCleanDir(this.config.tempPublicDir, this.config.maxTempPublicDirSize, 60*60*1000);//1 раз в час
|
this.periodicCleanDir(this.config.tempPublicDir, this.config.maxTempPublicDirSize, 60*60*1000);//1 раз в час
|
||||||
this.periodicCleanDir(this.config.uploadDir, this.config.maxUploadPublicDirSize, 60*60*1000);//1 раз в час
|
this.periodicCleanDir(this.config.uploadDir, this.config.maxUploadPublicDirSize, 60*60*1000);//1 раз в час
|
||||||
|
|
||||||
@@ -39,7 +45,6 @@ class ReaderWorker {
|
|||||||
|
|
||||||
async loadBook(opts, wState) {
|
async loadBook(opts, wState) {
|
||||||
const url = opts.url;
|
const url = opts.url;
|
||||||
let errMes = '';
|
|
||||||
let decompDir = '';
|
let decompDir = '';
|
||||||
let downloadedFilename = '';
|
let downloadedFilename = '';
|
||||||
let isUploaded = false;
|
let isUploaded = false;
|
||||||
@@ -87,17 +92,18 @@ class ReaderWorker {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//сжимаем файл в tmp, если там уже нет с тем же именем-sha256
|
//сжимаем файл в tmp, если там уже нет с тем же именем-sha256
|
||||||
const compFilename = await this.decomp.gzipFileIfNotExists(convertFilename, `${this.config.tempPublicDir}`);
|
const compFilename = await this.decomp.gzipFileIfNotExists(convertFilename, this.config.tempPublicDir);
|
||||||
|
const stat = await fs.stat(compFilename);
|
||||||
|
|
||||||
wState.set({progress: 100});
|
wState.set({progress: 100});
|
||||||
|
|
||||||
//finish
|
//finish
|
||||||
const finishFilename = path.basename(compFilename);
|
const finishFilename = path.basename(compFilename);
|
||||||
wState.finish({path: `/tmp/${finishFilename}`});
|
wState.finish({path: `/tmp/${finishFilename}`, size: stat.size});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(LM_ERR, e.stack);
|
log(LM_ERR, e.stack);
|
||||||
wState.set({state: 'error', error: (errMes ? errMes : e.message)});
|
wState.set({state: 'error', error: e.message});
|
||||||
} finally {
|
} finally {
|
||||||
//clean
|
//clean
|
||||||
if (decompDir)
|
if (decompDir)
|
||||||
@@ -133,6 +139,41 @@ class ReaderWorker {
|
|||||||
return `file://${hash}`;
|
return `file://${hash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restoreCachedFile(filename) {
|
||||||
|
const workerId = this.workerState.generateWorkerId();
|
||||||
|
const wState = this.workerState.getControl(workerId);
|
||||||
|
wState.set({state: 'start'});
|
||||||
|
|
||||||
|
(async() => {
|
||||||
|
try {
|
||||||
|
wState.set({state: 'download', step: 1, totalSteps: 1, path: filename, progress: 0});
|
||||||
|
|
||||||
|
const basename = path.basename(filename);
|
||||||
|
const targetName = `${this.config.tempPublicDir}/${basename}`;
|
||||||
|
|
||||||
|
if (!await fs.pathExists(targetName)) {
|
||||||
|
let found = false;
|
||||||
|
if (this.remoteWebDavStorage) {
|
||||||
|
found = await this.remoteWebDavStorage.getFileSuccess(targetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
throw new Error('404 Файл не найден');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stat = await fs.stat(targetName);
|
||||||
|
wState.finish({path: `/tmp/${basename}`, size: stat.size, progress: 100});
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message.indexOf('404') < 0)
|
||||||
|
log(LM_ERR, e.stack);
|
||||||
|
wState.set({state: 'error', error: e.message});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return workerId;
|
||||||
|
}
|
||||||
|
|
||||||
async periodicCleanDir(dir, maxSize, timeout) {
|
async periodicCleanDir(dir, maxSize, timeout) {
|
||||||
try {
|
try {
|
||||||
const list = await fs.readdir(dir);
|
const list = await fs.readdir(dir);
|
||||||
@@ -153,7 +194,16 @@ class ReaderWorker {
|
|||||||
let i = 0;
|
let i = 0;
|
||||||
while (i < files.length && size > maxSize) {
|
while (i < files.length && size > maxSize) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
await fs.remove(`${dir}/${file.name}`);
|
const oldFile = `${dir}/${file.name}`;
|
||||||
|
if (this.remoteWebDavStorage) {
|
||||||
|
try {
|
||||||
|
//log(`remoteWebDavStorage.putFile ${path.basename(oldFile)}`);
|
||||||
|
await this.remoteWebDavStorage.putFile(oldFile);
|
||||||
|
} catch (e) {
|
||||||
|
log(LM_ERR, e.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await fs.remove(oldFile);
|
||||||
size -= file.stat.size;
|
size -= file.stat.size;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|||||||
121
server/core/RemoteWebDavStorage.js
Normal file
121
server/core/RemoteWebDavStorage.js
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const WebDavFS = require('webdav-fs');
|
||||||
|
|
||||||
|
class RemoteWebDavStorage {
|
||||||
|
constructor(config) {
|
||||||
|
const opts = Object.assign({}, config);
|
||||||
|
this.wfs = WebDavFS(config.url, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
stat(filename) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.wfs.stat(filename, function(err, fileStat) {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
resolve(fileStat);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFile(filename, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.wfs.writeFile(filename, data, 'binary', function(err) {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink(filename) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.wfs.unlink(filename, function(err) {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile(filename) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.wfs.readFile(filename, 'binary', function(err, data) {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
resolve(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir(dirname) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.wfs.mkdir(dirname, function(err) {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async putFile(filename) {
|
||||||
|
if (!await fs.pathExists(filename)) {
|
||||||
|
throw new Error(`File not found: ${filename}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = path.basename(filename);
|
||||||
|
let remoteFilename = `/${base}`;
|
||||||
|
|
||||||
|
if (base.length > 3) {
|
||||||
|
const remoteDir = `/${base.substr(0, 3)}`;
|
||||||
|
try {
|
||||||
|
await this.mkdir(remoteDir);
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
remoteFilename = `${remoteDir}/${base}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const localStat = await fs.stat(filename);
|
||||||
|
const remoteStat = await this.stat(remoteFilename);
|
||||||
|
if (remoteStat.isFile && localStat.size == remoteStat.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.unlink(remoteFilename);
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await fs.readFile(filename);
|
||||||
|
await this.writeFile(remoteFilename, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFile(filename) {
|
||||||
|
if (await fs.pathExists(filename)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = path.basename(filename);
|
||||||
|
let remoteFilename = `/${base}`;
|
||||||
|
if (base.length > 3) {
|
||||||
|
remoteFilename = `/${base.substr(0, 3)}/${base}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await this.readFile(remoteFilename);
|
||||||
|
await fs.writeFile(filename, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFileSuccess(filename) {
|
||||||
|
try {
|
||||||
|
await this.getFile(filename);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RemoteWebDavStorage;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
require('tls').DEFAULT_MIN_VERSION = 'TLSv1';
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const argv = require('minimist')(process.argv.slice(2));
|
const argv = require('minimist')(process.argv.slice(2));
|
||||||
@@ -18,7 +19,7 @@ async function init() {
|
|||||||
const log = appLogger.log;
|
const log = appLogger.log;
|
||||||
|
|
||||||
//dirs
|
//dirs
|
||||||
log(`${config.name} v${config.version}`);
|
log(`${config.name} v${config.version}, Node.js ${process.version}`);
|
||||||
log('Initializing');
|
log('Initializing');
|
||||||
|
|
||||||
await fs.ensureDir(config.dataDir);
|
await fs.ensureDir(config.dataDir);
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ function initRoutes(app, config) {
|
|||||||
['POST', '/api/reader/load-book', reader.loadBook.bind(reader), [aAll], {}],
|
['POST', '/api/reader/load-book', reader.loadBook.bind(reader), [aAll], {}],
|
||||||
['POST', '/api/reader/storage', reader.storage.bind(reader), [aAll], {}],
|
['POST', '/api/reader/storage', reader.storage.bind(reader), [aAll], {}],
|
||||||
['POST', '/api/reader/upload-file', [upload.single('file'), reader.uploadFile.bind(reader)], [aAll], {}],
|
['POST', '/api/reader/upload-file', [upload.single('file'), reader.uploadFile.bind(reader)], [aAll], {}],
|
||||||
|
['POST', '/api/reader/restore-cached-file', reader.restoreCachedFile.bind(reader), [aAll], {}],
|
||||||
['POST', '/api/worker/get-state', worker.getState.bind(worker), [aAll], {}],
|
['POST', '/api/worker/get-state', worker.getState.bind(worker), [aAll], {}],
|
||||||
|
['POST', '/api/worker/get-state-finish', worker.getStateFinish.bind(worker), [aAll], {}],
|
||||||
];
|
];
|
||||||
|
|
||||||
//to app
|
//to app
|
||||||
|
|||||||
Reference in New Issue
Block a user