Compare commits
51 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 |
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
|
||||||
|
|||||||
@@ -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,53 +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 checkUrl(url) {
|
async checkUrl(url) {
|
||||||
return await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
|
let fileExists = false;
|
||||||
|
try {
|
||||||
|
await axios.head(url, {headers: {'Cache-Control': 'no-cache'}});
|
||||||
|
fileExists = true;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadCachedBook(url, callback) {
|
//восстановим при необходимости файл на сервере из удаленного облака
|
||||||
const response = await axios.head(url);
|
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;
|
||||||
@@ -77,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 = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container>
|
<el-container>
|
||||||
<div v-show="isShowMigrationWarning" style="height: 30px; overflow: hidden; white-space:nowrap;">
|
|
||||||
<span style="margin-left: 10px; font-size: 20px; position: relative; top: 2px" class="clickable" @click="showMigrationHttp">HTTP-версия читалки скоро станет недоступна. Подробнее...</span>
|
|
||||||
</div>
|
|
||||||
<el-header v-show="toolBarActive" height='50px'>
|
<el-header v-show="toolBarActive" height='50px'>
|
||||||
<div ref="header" class="header">
|
<div ref="header" class="header">
|
||||||
<el-tooltip content="Загрузить книгу" :open-delay="1000" effect="light">
|
<el-tooltip content="Загрузить книгу" :open-delay="1000" effect="light">
|
||||||
@@ -93,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>
|
||||||
|
|
||||||
@@ -285,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;
|
||||||
@@ -346,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();
|
||||||
})();
|
})();
|
||||||
@@ -360,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;
|
||||||
|
|
||||||
@@ -426,41 +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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get isShowMigrationWarning() {
|
|
||||||
return window.location.protocol == 'http:';
|
|
||||||
}
|
|
||||||
|
|
||||||
showMigrationHttp() {
|
|
||||||
this.migrationVisible1 = 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();
|
||||||
@@ -579,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) {
|
||||||
@@ -952,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;
|
||||||
|
|||||||
@@ -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() => {
|
||||||
|
|||||||
@@ -1,4 +1,39 @@
|
|||||||
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',
|
showUntil: '2019-11-26',
|
||||||
header: '0.7.9 (2019-11-27)',
|
header: '0.7.9 (2019-11-27)',
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
324
package-lock.json
generated
324
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Liberama",
|
"name": "Liberama",
|
||||||
"version": "0.7.9",
|
"version": "0.8.1",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1725,6 +1725,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"base-64": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||||
|
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
|
||||||
|
},
|
||||||
"base-x": {
|
"base-x": {
|
||||||
"version": "3.0.7",
|
"version": "3.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.7.tgz",
|
||||||
@@ -2013,24 +2018,37 @@
|
|||||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||||
},
|
},
|
||||||
"cacache": {
|
"cacache": {
|
||||||
"version": "10.0.4",
|
"version": "12.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz",
|
||||||
"integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==",
|
"integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bluebird": "^3.5.1",
|
"bluebird": "^3.5.5",
|
||||||
"chownr": "^1.0.1",
|
"chownr": "^1.1.1",
|
||||||
"glob": "^7.1.2",
|
"figgy-pudding": "^3.5.1",
|
||||||
"graceful-fs": "^4.1.11",
|
"glob": "^7.1.4",
|
||||||
"lru-cache": "^4.1.1",
|
"graceful-fs": "^4.1.15",
|
||||||
"mississippi": "^2.0.0",
|
"infer-owner": "^1.0.3",
|
||||||
|
"lru-cache": "^5.1.1",
|
||||||
|
"mississippi": "^3.0.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"move-concurrently": "^1.0.1",
|
"move-concurrently": "^1.0.1",
|
||||||
"promise-inflight": "^1.0.1",
|
"promise-inflight": "^1.0.1",
|
||||||
"rimraf": "^2.6.2",
|
"rimraf": "^2.6.3",
|
||||||
"ssri": "^5.2.4",
|
"ssri": "^6.0.1",
|
||||||
"unique-filename": "^1.1.0",
|
"unique-filename": "^1.1.1",
|
||||||
"y18n": "^4.0.0"
|
"y18n": "^4.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"yallist": "^3.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cache-base": {
|
"cache-base": {
|
||||||
@@ -2689,19 +2707,104 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"copy-webpack-plugin": {
|
"copy-webpack-plugin": {
|
||||||
"version": "4.6.0",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz",
|
||||||
"integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==",
|
"integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"cacache": "^10.0.4",
|
"cacache": "^12.0.3",
|
||||||
"find-cache-dir": "^1.0.0",
|
"find-cache-dir": "^2.1.0",
|
||||||
|
"glob-parent": "^3.1.0",
|
||||||
"globby": "^7.1.1",
|
"globby": "^7.1.1",
|
||||||
"is-glob": "^4.0.0",
|
"is-glob": "^4.0.1",
|
||||||
"loader-utils": "^1.1.0",
|
"loader-utils": "^1.2.3",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"p-limit": "^1.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
"serialize-javascript": "^1.4.0"
|
"p-limit": "^2.2.1",
|
||||||
|
"schema-utils": "^1.0.0",
|
||||||
|
"serialize-javascript": "^2.1.2",
|
||||||
|
"webpack-log": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"find-cache-dir": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"commondir": "^1.0.1",
|
||||||
|
"make-dir": "^2.0.0",
|
||||||
|
"pkg-dir": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"find-up": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"locate-path": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locate-path": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"p-locate": "^3.0.0",
|
||||||
|
"path-exists": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"make-dir": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"pify": "^4.0.1",
|
||||||
|
"semver": "^5.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"p-limit": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"p-try": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"p-locate": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"p-limit": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"p-try": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"pify": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"pkg-dir": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"find-up": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"core-js": {
|
"core-js": {
|
||||||
@@ -3512,12 +3615,6 @@
|
|||||||
"path-type": "^3.0.0"
|
"path-type": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"disable-output-webpack-plugin": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/disable-output-webpack-plugin/-/disable-output-webpack-plugin-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-fT4qiosyTqteTKDbYLXISMKzMVbl2dMXbMpUN3VPAUxX9JL81MmOilisbX555W1XS4B6FRzQt9zMtqg1T6Jx9Q==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"doctrine": {
|
"doctrine": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||||
@@ -4081,12 +4178,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||||
},
|
},
|
||||||
"event-hooks-webpack-plugin": {
|
|
||||||
"version": "2.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/event-hooks-webpack-plugin/-/event-hooks-webpack-plugin-2.1.4.tgz",
|
|
||||||
"integrity": "sha512-TmxHNmRHPIG4XVDtAKT6i/rXNTAR6EnFQlSVsBFMkQ1f0MT2Me9RBNLw4E1JvdH433ag1VF9tPkp5sjTOfZM4Q==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"events": {
|
"events": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
|
||||||
@@ -5633,6 +5724,11 @@
|
|||||||
"parse-passwd": "^1.0.0"
|
"parse-passwd": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hot-patcher": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hot-patcher/-/hot-patcher-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-2Uu2W0s8+dnqXzdlg0MRsRzPoDCs1wVjOGSyMRRaMzLDX4bgHw6xDYKccsWafXPPxQpkQfEjgW6+17pwcg60bw=="
|
||||||
|
},
|
||||||
"hsl-regex": {
|
"hsl-regex": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz",
|
||||||
@@ -6816,6 +6912,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"merge": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ=="
|
||||||
|
},
|
||||||
"merge-descriptors": {
|
"merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
@@ -6963,9 +7064,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mississippi": {
|
"mississippi": {
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||||
"integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==",
|
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"concat-stream": "^1.5.0",
|
"concat-stream": "^1.5.0",
|
||||||
@@ -6974,22 +7075,10 @@
|
|||||||
"flush-write-stream": "^1.0.0",
|
"flush-write-stream": "^1.0.0",
|
||||||
"from2": "^2.1.0",
|
"from2": "^2.1.0",
|
||||||
"parallel-transform": "^1.1.0",
|
"parallel-transform": "^1.1.0",
|
||||||
"pump": "^2.0.1",
|
"pump": "^3.0.0",
|
||||||
"pumpify": "^1.3.3",
|
"pumpify": "^1.3.3",
|
||||||
"stream-each": "^1.1.0",
|
"stream-each": "^1.1.0",
|
||||||
"through2": "^2.0.0"
|
"through2": "^2.0.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"pump": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"once": "^1.3.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mixin-deep": {
|
"mixin-deep": {
|
||||||
@@ -7414,12 +7503,6 @@
|
|||||||
"boolbase": "~1.0.0"
|
"boolbase": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"null-loader": {
|
|
||||||
"version": "0.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/null-loader/-/null-loader-0.1.1.tgz",
|
|
||||||
"integrity": "sha1-F76av80/8OFRL2/Er8sfUDk3j64=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||||
@@ -7814,6 +7897,11 @@
|
|||||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"path-posix": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-BrJhE/Vr6rBCVFojv6iAA8ysJg8="
|
||||||
|
},
|
||||||
"path-to-regexp": {
|
"path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
@@ -10355,6 +10443,11 @@
|
|||||||
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
|
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"querystringify": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
|
||||||
|
},
|
||||||
"randombytes": {
|
"randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
@@ -10641,6 +10734,11 @@
|
|||||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"requires-port": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
|
||||||
|
},
|
||||||
"resize-observer-polyfill": {
|
"resize-observer-polyfill": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||||
@@ -10883,9 +10981,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"serialize-javascript": {
|
"serialize-javascript": {
|
||||||
"version": "1.9.1",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||||
"integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
|
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"serve-static": {
|
"serve-static": {
|
||||||
@@ -11247,12 +11345,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ssri": {
|
"ssri": {
|
||||||
"version": "5.3.0",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||||
"integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==",
|
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.1"
|
"figgy-pudding": "^3.5.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stable": {
|
"stable": {
|
||||||
@@ -11780,9 +11878,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"terser": {
|
"terser": {
|
||||||
"version": "4.3.1",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-4.4.3.tgz",
|
||||||
"integrity": "sha512-pnzH6dnFEsR2aa2SJaKb1uSCl3QmIsJ8dEkj0Fky+2AwMMcC9doMqLOQIH6wVTEKaVfKVvLSk5qxPBEZT9mywg==",
|
"integrity": "sha512-0ikKraVtRDKGzHrzkCv5rUNDzqlhmhowOBqC0XqUHFpW+vJ45+20/IFBcebwKfiS2Z9fJin6Eo+F1zLZsxi8RA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"commander": "^2.20.0",
|
"commander": "^2.20.0",
|
||||||
@@ -11791,9 +11889,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "2.20.0",
|
"version": "2.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
@@ -11803,9 +11901,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"source-map-support": {
|
"source-map-support": {
|
||||||
"version": "0.5.13",
|
"version": "0.5.16",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
|
||||||
"integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
|
"integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"buffer-from": "^1.0.0",
|
"buffer-from": "^1.0.0",
|
||||||
@@ -11815,16 +11913,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"terser-webpack-plugin": {
|
"terser-webpack-plugin": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
|
||||||
"integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==",
|
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"cacache": "^12.0.2",
|
"cacache": "^12.0.2",
|
||||||
"find-cache-dir": "^2.1.0",
|
"find-cache-dir": "^2.1.0",
|
||||||
"is-wsl": "^1.1.0",
|
"is-wsl": "^1.1.0",
|
||||||
"schema-utils": "^1.0.0",
|
"schema-utils": "^1.0.0",
|
||||||
"serialize-javascript": "^1.7.0",
|
"serialize-javascript": "^2.1.2",
|
||||||
"source-map": "^0.6.1",
|
"source-map": "^0.6.1",
|
||||||
"terser": "^4.1.2",
|
"terser": "^4.1.2",
|
||||||
"webpack-sources": "^1.4.0",
|
"webpack-sources": "^1.4.0",
|
||||||
@@ -11922,9 +12020,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"p-limit": {
|
"p-limit": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
|
||||||
"integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
|
"integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"p-try": "^2.0.0"
|
"p-try": "^2.0.0"
|
||||||
@@ -11960,6 +12058,12 @@
|
|||||||
"find-up": "^3.0.0"
|
"find-up": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"serialize-javascript": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
@@ -12415,6 +12519,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"url-join": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
|
||||||
|
},
|
||||||
"url-loader": {
|
"url-loader": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz",
|
||||||
@@ -12434,6 +12543,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"url-parse": {
|
||||||
|
"version": "1.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
|
||||||
|
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
|
||||||
|
"requires": {
|
||||||
|
"querystringify": "^2.1.1",
|
||||||
|
"requires-port": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"url-parse-lax": {
|
"url-parse-lax": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
|
||||||
@@ -12663,6 +12781,40 @@
|
|||||||
"neo-async": "^2.5.0"
|
"neo-async": "^2.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"webdav": {
|
||||||
|
"version": "2.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webdav/-/webdav-2.10.1.tgz",
|
||||||
|
"integrity": "sha512-3UfnjGTAqSM9MW3Rpt1KrY1KneYK0wPCFryHTncqw1OP1pyiniT3uYhVpgmH6za/TkWOfnTnKCDKhwrLJFdzow==",
|
||||||
|
"requires": {
|
||||||
|
"axios": "^0.19.0",
|
||||||
|
"base-64": "^0.1.0",
|
||||||
|
"hot-patcher": "^0.5.0",
|
||||||
|
"merge": "^1.2.1",
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
|
"path-posix": "^1.0.0",
|
||||||
|
"url-join": "^4.0.1",
|
||||||
|
"url-parse": "^1.4.7",
|
||||||
|
"xml2js": "^0.4.19"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": {
|
||||||
|
"version": "0.19.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.1.tgz",
|
||||||
|
"integrity": "sha512-Yl+7nfreYKaLRvAvjNPkvfjnQHJM1yLBY3zhqAwcJSwR/6ETkanUgylgtIvkvz0xJ+p/vZuNw8X7Hnb7Whsbpw==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "1.5.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webdav-fs": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webdav-fs/-/webdav-fs-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-TjqQKNnf1NuPiMEFJVrmWUYpIEgUxWRktddu5JhQsyxFIOyAJT0cpLyaoOdAp/yrG1yXgDsa6ZL9z+h4Z71zWA==",
|
||||||
|
"requires": {
|
||||||
|
"webdav": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"webpack": {
|
"webpack": {
|
||||||
"version": "4.40.2",
|
"version": "4.40.2",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.40.2.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.40.2.tgz",
|
||||||
@@ -12931,6 +13083,20 @@
|
|||||||
"mkdirp": "^0.5.1"
|
"mkdirp": "^0.5.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"xml2js": {
|
||||||
|
"version": "0.4.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||||
|
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
||||||
|
"requires": {
|
||||||
|
"sax": ">=0.6.0",
|
||||||
|
"xmlbuilder": "~11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xmlbuilder": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
||||||
|
},
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "Liberama",
|
"name": "Liberama",
|
||||||
"version": "0.7.9",
|
"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,19 +30,16 @@
|
|||||||
"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.4.2",
|
"pkg": "^4.4.2",
|
||||||
"terser-webpack-plugin": "^1.4.1",
|
"terser-webpack-plugin": "^1.4.1",
|
||||||
@@ -84,6 +84,7 @@
|
|||||||
"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;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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