Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f36c13fea1 | ||
|
|
4fd9d579e0 | ||
|
|
e65a8a13ea | ||
|
|
6ddb97d43e | ||
|
|
89082603de | ||
|
|
a9a3227433 | ||
|
|
60cb3514b2 | ||
|
|
4aeaa05f0b | ||
|
|
9c06552278 | ||
|
|
000f8dde82 | ||
|
|
9ffc218002 | ||
|
|
68a188f099 | ||
|
|
8829bb3810 | ||
|
|
5164d2f536 | ||
|
|
451538fcf7 | ||
|
|
82a02ef339 | ||
|
|
b834d4951f | ||
|
|
edc3b669be | ||
|
|
522826311d | ||
|
|
e69b9951d5 | ||
|
|
c6300222ea | ||
|
|
5aa6ee899c | ||
|
|
4b76f97d2b | ||
|
|
5ccfe71c55 | ||
|
|
97fc902cdb | ||
|
|
7e935951d7 | ||
|
|
810c6d68d2 | ||
|
|
003dc70f4f | ||
|
|
371ff64a95 | ||
|
|
b0de5adbf3 | ||
|
|
d1d2b07c33 | ||
|
|
d9b2444c1a | ||
|
|
e7fae27031 | ||
|
|
eb0c7b0a32 | ||
|
|
3d7ad0dd9a | ||
|
|
ae04feb311 |
@@ -120,32 +120,7 @@ class Reader {
|
||||
estSize = response.headers['content-length'];
|
||||
}
|
||||
} catch (e) {
|
||||
//восстановим при необходимости файл на сервере из удаленного облака
|
||||
let response = null
|
||||
|
||||
try {
|
||||
response = await wsc.message(await wsc.send({action: 'reader-restore-cached-file', path: url}));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
//если с WebSocket проблема, работаем по http
|
||||
response = await api.post('/restore-cached-file', {path: url});
|
||||
response = response.data;
|
||||
}
|
||||
if (response.state == 'error') {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
const workerId = response.workerId;
|
||||
if (!workerId)
|
||||
throw new Error('Неверный ответ api');
|
||||
|
||||
response = await this.getWorkerStateFinish(workerId);
|
||||
if (response.state == 'error') {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.size && estSize < 0) {
|
||||
estSize = response.size;
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
return estSize;
|
||||
@@ -226,16 +201,18 @@ class Reader {
|
||||
return response;
|
||||
}
|
||||
|
||||
async uploadFileBuf(buf, urlCallback) {
|
||||
makeUrlFromBuf(buf) {
|
||||
const key = utils.toHex(cryptoUtils.sha256(buf));
|
||||
const url = `disk://${key}`;
|
||||
return `disk://${key}`;
|
||||
}
|
||||
|
||||
if (urlCallback)
|
||||
urlCallback(url);
|
||||
async uploadFileBuf(buf, url) {
|
||||
if (!url)
|
||||
url = this.makeUrlFromBuf(buf);
|
||||
|
||||
let response;
|
||||
try {
|
||||
await axios.head(`/upload/${key}`, {headers: {'Cache-Control': 'no-cache'}});
|
||||
await axios.head(url.replace('disk://', '/upload/'), {headers: {'Cache-Control': 'no-cache'}});
|
||||
response = await wsc.message(await wsc.send({action: 'upload-file-touch', url}));
|
||||
} catch (e) {
|
||||
response = await wsc.message(await wsc.send({action: 'upload-file-buf', buf}));
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row-part column justify-center items-stretch" style="width: 80px">
|
||||
<div class="col row justify-center items-center clickable" style="padding: 4px" @click="loadBook(item)">
|
||||
<div class="col row justify-center items-center clickable" style="padding: 0 2px 0 2px" @click="loadBook(item)">
|
||||
<div v-show="isLoadedCover(item.coverPageUrl)" style="height: 80px" v-html="getCoverHtml(item.coverPageUrl)" />
|
||||
<q-icon v-show="!isLoadedCover(item.coverPageUrl)" name="la la-book" size="40px" style="color: #dddddd" />
|
||||
</div>
|
||||
@@ -243,6 +243,7 @@ class RecentBooksPage {
|
||||
archive = false;
|
||||
|
||||
covers = {};
|
||||
coversLoadFunc = {};
|
||||
|
||||
created() {
|
||||
this.commit = this.$store.commit;
|
||||
@@ -669,20 +670,36 @@ class RecentBooksPage {
|
||||
return false;
|
||||
|
||||
let loadedCover = this.covers[coverPageUrl];
|
||||
|
||||
if (loadedCover == 'error')
|
||||
return false;
|
||||
|
||||
if (!loadedCover) {
|
||||
(async() => {
|
||||
//сначала заглянем в storage
|
||||
let data = await coversStorage.getData(coverPageUrl);
|
||||
if (data) {
|
||||
this.covers[coverPageUrl] = this.makeCoverHtml(data);
|
||||
} else {//иначе идем на сервер
|
||||
try {
|
||||
data = await readerApi.getUploadedFileBuf(coverPageUrl);
|
||||
await coversStorage.setData(coverPageUrl, data);
|
||||
this.covers[coverPageUrl] = this.makeCoverHtml(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (this.coversLoadFunc[coverPageUrl])
|
||||
return;
|
||||
|
||||
this.coversLoadFunc[coverPageUrl] = (async() => {
|
||||
//сначала заглянем в storage
|
||||
let data = await coversStorage.getData(coverPageUrl);
|
||||
if (data) {
|
||||
this.covers[coverPageUrl] = this.makeCoverHtml(data);
|
||||
} else {//иначе идем на сервер
|
||||
try {
|
||||
data = await readerApi.getUploadedFileBuf(coverPageUrl);
|
||||
await coversStorage.setData(coverPageUrl, data);
|
||||
this.covers[coverPageUrl] = this.makeCoverHtml(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.covers[coverPageUrl] = 'error';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await this.coversLoadFunc[coverPageUrl]();
|
||||
} finally {
|
||||
this.coversLoadFunc[coverPageUrl] = null;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<template>
|
||||
<Window ref="window" height="140px" max-width="600px" :top-shift="-50" @close="close">
|
||||
<Window ref="window" height="125px" max-width="600px" :top-shift="-50" @close="close">
|
||||
<template #header>
|
||||
Установить позицию
|
||||
</template>
|
||||
|
||||
<div id="set-position-slider" class="slider q-px-md">
|
||||
<q-slider
|
||||
v-model="sliderValue"
|
||||
thumb-path="M 2, 10 a 8.5,8.5 0 1,0 17,0 a 8.5,8.5 0 1,0 -17,0"
|
||||
|
||||
:max="sliderMax"
|
||||
label
|
||||
:label-value="(sliderMax ? (sliderValue/sliderMax*100).toFixed(2) + '%' : 0)"
|
||||
color="primary"
|
||||
/>
|
||||
<div class="col column justify-center">
|
||||
<div id="set-position-slider" class="slider q-px-md column justify-center">
|
||||
<q-slider
|
||||
v-model="sliderValue"
|
||||
thumb-path="M 2, 10 a 8.5,8.5 0 1,0 17,0 a 8.5,8.5 0 1,0 -17,0"
|
||||
|
||||
:max="sliderMax"
|
||||
label
|
||||
:label-value="(sliderMax ? (sliderValue/sliderMax*100).toFixed(2) + '%' : 0)"
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Window>
|
||||
</template>
|
||||
@@ -76,7 +78,8 @@ export default vueComponent(SetPositionPage);
|
||||
|
||||
<style scoped>
|
||||
.slider {
|
||||
margin: 20px;
|
||||
margin: 0 20px 0 20px;
|
||||
height: 35px;
|
||||
background-color: #efefef;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
</template>
|
||||
|
||||
<div class="col row">
|
||||
<a ref="download" style="display: none;" target="_blank"></a>
|
||||
|
||||
<div class="full-height">
|
||||
<q-tabs
|
||||
ref="tabs"
|
||||
@@ -674,6 +676,27 @@ class SettingsPage {
|
||||
}
|
||||
}
|
||||
|
||||
async downloadWallpaper() {
|
||||
if (this.wallpaper.indexOf('user-paper') != 0)
|
||||
return;
|
||||
|
||||
try {
|
||||
const d = this.$refs.download;
|
||||
|
||||
const dataUrl = await wallpaperStorage.getData(this.wallpaper);
|
||||
|
||||
if (!dataUrl)
|
||||
throw new Error('Файл обоев не найден');
|
||||
|
||||
d.href = dataUrl;
|
||||
d.download = `wallpaper-#${this.wallpaper.replace('user-paper', '').substring(0, 4)}`;
|
||||
|
||||
d.click();
|
||||
} catch (e) {
|
||||
this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
|
||||
}
|
||||
}
|
||||
|
||||
keyHook(event) {
|
||||
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
|
||||
this.close();
|
||||
|
||||
@@ -102,6 +102,11 @@
|
||||
Удалить выбранные обои
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn v-show="wallpaper.indexOf('user-paper') === 0" class="q-ml-sm" round dense color="blue" icon="la la-file-download" @click.stop="downloadWallpaper">
|
||||
<q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%">
|
||||
Скачать выбранные обои
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -359,18 +359,20 @@ class BookManager {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
//отправим dataUrl на сервер в /upload
|
||||
try {
|
||||
await readerApi.uploadFileBuf(dataUrl, (url) => {
|
||||
coverPageUrl = url;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
coverPageUrl = readerApi.makeUrlFromBuf(dataUrl);
|
||||
|
||||
//сохраним в storage
|
||||
if (coverPageUrl)
|
||||
//далее асинхронно
|
||||
(async() => {
|
||||
//отправим dataUrl на сервер в /upload
|
||||
try {
|
||||
await readerApi.uploadFileBuf(dataUrl, coverPageUrl);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
//сохраним в storage
|
||||
await coversStorage.setData(coverPageUrl, dataUrl);
|
||||
})();
|
||||
}
|
||||
|
||||
const result = Object.assign({}, meta, parsedMeta, {
|
||||
|
||||
@@ -6,6 +6,7 @@ server {
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
server_name beta.liberama.top;
|
||||
set $liberama http://127.0.0.1:34082;
|
||||
|
||||
client_max_body_size 50m;
|
||||
proxy_read_timeout 1h;
|
||||
@@ -15,15 +16,20 @@ server {
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types *;
|
||||
|
||||
location @liberama {
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:34082;
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:34082;
|
||||
proxy_pass $liberama;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
location / {
|
||||
@@ -32,6 +38,11 @@ server {
|
||||
location /tmp {
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location /upload {
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location ~* \.(?:manifest|appcache|html)$ {
|
||||
@@ -50,6 +61,7 @@ server {
|
||||
server {
|
||||
listen 80;
|
||||
server_name b.beta.liberama.top;
|
||||
set $liberama http://127.0.0.1:34082;
|
||||
|
||||
client_max_body_size 50m;
|
||||
proxy_read_timeout 1h;
|
||||
@@ -59,15 +71,20 @@ server {
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types *;
|
||||
|
||||
location @liberama {
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:34082;
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:34082;
|
||||
proxy_pass $liberama;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
location / {
|
||||
@@ -76,6 +93,11 @@ server {
|
||||
location /tmp {
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location /upload {
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location ~* \.(?:manifest|appcache|html)$ {
|
||||
|
||||
@@ -6,6 +6,7 @@ server {
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
server_name beta.omnireader.ru;
|
||||
set $liberama http://127.0.0.1:34081;
|
||||
|
||||
client_max_body_size 50m;
|
||||
proxy_read_timeout 1h;
|
||||
@@ -15,15 +16,20 @@ server {
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types *;
|
||||
|
||||
location @liberama {
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:34081;
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:34081;
|
||||
proxy_pass $liberama;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
location / {
|
||||
@@ -32,6 +38,11 @@ server {
|
||||
location /tmp {
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location /upload {
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location ~* \.(?:manifest|appcache|html)$ {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name beta.omnireader.ru;
|
||||
set $liberama http://127.0.0.1:34081;
|
||||
|
||||
client_max_body_size 50m;
|
||||
proxy_read_timeout 1h;
|
||||
@@ -10,15 +11,20 @@ server {
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types *;
|
||||
|
||||
location @liberama {
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:34081;
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:34081;
|
||||
proxy_pass $liberama;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
location / {
|
||||
@@ -27,6 +33,11 @@ server {
|
||||
location /tmp {
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location /upload {
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location ~* \.(?:manifest|appcache|html)$ {
|
||||
|
||||
@@ -17,6 +17,7 @@ server {
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
server_name liberama.top;
|
||||
set $liberama http://127.0.0.1:55081;
|
||||
|
||||
client_max_body_size 100m;
|
||||
proxy_read_timeout 1h;
|
||||
@@ -26,12 +27,16 @@ server {
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types *;
|
||||
|
||||
location @liberama {
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:55081;
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:55081;
|
||||
proxy_pass $liberama;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
@@ -44,6 +49,11 @@ server {
|
||||
location /tmp {
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location /upload {
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location ~* \.(?:manifest|appcache|html)$ {
|
||||
@@ -62,6 +72,7 @@ server {
|
||||
server {
|
||||
listen 80;
|
||||
server_name b.liberama.top;
|
||||
set $liberama http://127.0.0.1:55081;
|
||||
|
||||
client_max_body_size 100m;
|
||||
proxy_read_timeout 1h;
|
||||
@@ -71,15 +82,20 @@ server {
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types *;
|
||||
|
||||
location @liberama {
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:55081;
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:55081;
|
||||
proxy_pass $liberama;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
location / {
|
||||
@@ -88,6 +104,11 @@ server {
|
||||
location /tmp {
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location /upload {
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location ~* \.(?:manifest|appcache|html)$ {
|
||||
|
||||
@@ -6,6 +6,7 @@ server {
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
server_name omnireader.ru;
|
||||
set $liberama http://127.0.0.1:44081;
|
||||
|
||||
client_max_body_size 100m;
|
||||
proxy_read_timeout 1h;
|
||||
@@ -15,12 +16,16 @@ server {
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types *;
|
||||
|
||||
location @liberama {
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:44081;
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:44081;
|
||||
proxy_pass $liberama;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
@@ -33,6 +38,11 @@ server {
|
||||
location /tmp {
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location /upload {
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location ~* \.(?:manifest|appcache|html)$ {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name omnireader.ru;
|
||||
set $liberama http://127.0.0.1:44081;
|
||||
|
||||
client_max_body_size 50m;
|
||||
proxy_read_timeout 1h;
|
||||
@@ -10,12 +11,16 @@ server {
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types *;
|
||||
|
||||
location @liberama {
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://127.0.0.1:44081;
|
||||
proxy_pass $liberama;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:44081;
|
||||
proxy_pass $liberama;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
@@ -27,6 +32,11 @@ server {
|
||||
location /tmp {
|
||||
types { } default_type "application/xml; charset=utf-8";
|
||||
add_header Content-Encoding gzip;
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location /upload {
|
||||
try_files $uri @liberama;
|
||||
}
|
||||
|
||||
location ~* \.(?:manifest|appcache|html)$ {
|
||||
|
||||
@@ -39,6 +39,11 @@ module.exports = {
|
||||
],
|
||||
|
||||
jembaDb: [
|
||||
{
|
||||
dbName: 'app',
|
||||
thread: true,
|
||||
openAll: true,
|
||||
},
|
||||
{
|
||||
dbName: 'reader-storage',
|
||||
thread: true,
|
||||
@@ -49,14 +54,14 @@ module.exports = {
|
||||
servers: [
|
||||
{
|
||||
serverName: '1',
|
||||
mode: 'normal', //'none', 'normal', 'site', 'reader', 'omnireader', 'liberama.top'
|
||||
mode: 'normal', //'none', 'normal', 'site', 'reader', 'omnireader', 'liberama.top', 'book_update_checker'
|
||||
ip: '0.0.0.0',
|
||||
port: '33080',
|
||||
},
|
||||
],
|
||||
|
||||
remoteWebDavStorage: false,
|
||||
/*
|
||||
remoteWebDavStorage: false,
|
||||
remoteWebDavStorage: {
|
||||
url: '127.0.0.1:1900',
|
||||
username: '',
|
||||
@@ -64,5 +69,12 @@ module.exports = {
|
||||
},
|
||||
*/
|
||||
|
||||
remoteStorage: false,
|
||||
/*
|
||||
remoteStorage: {
|
||||
url: 'https://127.0.0.1:11900',
|
||||
accessToken: '',
|
||||
},
|
||||
*/
|
||||
};
|
||||
|
||||
|
||||
95
server/controllers/BookUpdateCheckerController.js
Normal file
95
server/controllers/BookUpdateCheckerController.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const WebSocket = require ('ws');
|
||||
//const _ = require('lodash');
|
||||
|
||||
const log = new (require('../core/AppLogger'))().log;//singleton
|
||||
//const utils = require('../core/utils');
|
||||
|
||||
const cleanPeriod = 1*60*1000;//1 минута
|
||||
const closeSocketOnIdle = 5*60*1000;//5 минут
|
||||
|
||||
class BookUpdateCheckerController {
|
||||
constructor(wss, config) {
|
||||
this.config = config;
|
||||
this.isDevelopment = (config.branch == 'development');
|
||||
|
||||
//this.readerStorage = new JembaReaderStorage();
|
||||
|
||||
this.wss = wss;
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
ws.on('message', (message) => {
|
||||
this.onMessage(ws, message.toString());
|
||||
});
|
||||
|
||||
ws.on('error', (err) => {
|
||||
log(LM_ERR, err);
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
||||
}
|
||||
|
||||
periodicClean() {
|
||||
try {
|
||||
const now = Date.now();
|
||||
this.wss.clients.forEach((ws) => {
|
||||
if (!ws.lastActivity || now - ws.lastActivity > closeSocketOnIdle - 50) {
|
||||
ws.terminate();
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
setTimeout(() => { this.periodicClean(); }, cleanPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
async onMessage(ws, message) {
|
||||
let req = {};
|
||||
try {
|
||||
if (this.isDevelopment) {
|
||||
log(`WebSocket-IN: ${message.substr(0, 4000)}`);
|
||||
}
|
||||
|
||||
req = JSON.parse(message);
|
||||
|
||||
ws.lastActivity = Date.now();
|
||||
|
||||
//pong for WebSocketConnection
|
||||
this.send({_rok: 1}, req, ws);
|
||||
|
||||
switch (req.action) {
|
||||
case 'test':
|
||||
await this.test(req, ws); break;
|
||||
|
||||
default:
|
||||
throw new Error(`Action not found: ${req.action}`);
|
||||
}
|
||||
} catch (e) {
|
||||
this.send({error: e.message}, req, ws);
|
||||
}
|
||||
}
|
||||
|
||||
send(res, req, ws) {
|
||||
if (ws.readyState == WebSocket.OPEN) {
|
||||
ws.lastActivity = Date.now();
|
||||
let r = res;
|
||||
if (req.requestId)
|
||||
r = Object.assign({requestId: req.requestId}, r);
|
||||
|
||||
const message = JSON.stringify(r);
|
||||
ws.send(message);
|
||||
|
||||
if (this.isDevelopment) {
|
||||
log(`WebSocket-OUT: ${message.substr(0, 4000)}`);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Actions ------------------------------------------------------------------
|
||||
async test(req, ws) {
|
||||
this.send({message: 'Liberama project is awesome'}, req, ws);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = BookUpdateCheckerController;
|
||||
@@ -68,24 +68,6 @@ class ReaderController extends BaseController {
|
||||
res.status(400).send({error});
|
||||
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;
|
||||
|
||||
@@ -70,8 +70,6 @@ class WebSocketController {
|
||||
await this.workerGetState(req, ws); break;
|
||||
case 'worker-get-state-finish':
|
||||
await this.workerGetStateFinish(req, ws); break;
|
||||
case 'reader-restore-cached-file':
|
||||
await this.readerRestoreCachedFile(req, ws); break;
|
||||
case 'reader-storage':
|
||||
await this.readerStorageDo(req, ws); break;
|
||||
case 'upload-file-buf':
|
||||
@@ -157,15 +155,6 @@ class WebSocketController {
|
||||
}
|
||||
}
|
||||
|
||||
async readerRestoreCachedFile(req, ws) {
|
||||
if (!req.path)
|
||||
throw new Error(`key 'path' is empty`);
|
||||
|
||||
const workerId = this.readerWorker.restoreCachedFile(req.path);
|
||||
const state = this.workerState.getState(workerId);
|
||||
this.send((state ? state : {}), req, ws);
|
||||
}
|
||||
|
||||
async readerStorageDo(req, ws) {
|
||||
if (!req.body)
|
||||
throw new Error(`key 'body' is empty`);
|
||||
|
||||
@@ -3,4 +3,5 @@ module.exports = {
|
||||
ReaderController: require('./ReaderController'),
|
||||
WorkerController: require('./WorkerController'),
|
||||
WebSocketController: require('./WebSocketController'),
|
||||
BookUpdateCheckerController: require('./BookUpdateCheckerController'),
|
||||
}
|
||||
0
server/core/BookUpdateChecker/BUCClient.js
Normal file
0
server/core/BookUpdateChecker/BUCClient.js
Normal file
24
server/core/BookUpdateChecker/BUCServer.js
Normal file
24
server/core/BookUpdateChecker/BUCServer.js
Normal file
@@ -0,0 +1,24 @@
|
||||
let instance = null;
|
||||
|
||||
//singleton
|
||||
class BUCServer {
|
||||
constructor(config) {
|
||||
if (!instance) {
|
||||
this.config = Object.assign({}, config);
|
||||
|
||||
this.config.tempDownloadDir = `${config.tempDir}/download`;
|
||||
fs.ensureDirSync(this.config.tempDownloadDir);
|
||||
|
||||
this.down = new FileDownloader(config.maxUploadFileSize);
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
async main() {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BUCServer;
|
||||
@@ -23,7 +23,7 @@ class FileDownloader {
|
||||
estSize = res.headers['content-length'];
|
||||
}
|
||||
|
||||
if (estSize > this.limitDownloadSize) {
|
||||
if (this.limitDownloadSize && estSize > this.limitDownloadSize) {
|
||||
throw new Error('Файл слишком большой');
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@ const WorkerState = require('../WorkerState');//singleton
|
||||
const FileDownloader = require('../FileDownloader');
|
||||
const FileDecompressor = require('../FileDecompressor');
|
||||
const BookConverter = require('./BookConverter');
|
||||
const RemoteWebDavStorage = require('../RemoteWebDavStorage');
|
||||
const RemoteStorage = require('../RemoteStorage');
|
||||
const JembaConnManager = require('../../db/JembaConnManager');//singleton
|
||||
const ayncExit = new (require('../AsyncExit'))();
|
||||
|
||||
const utils = require('../utils');
|
||||
const log = new (require('../AppLogger'))().log;//singleton
|
||||
|
||||
const cleanDirPeriod = 60*60*1000;//1 раз в час
|
||||
const cleanDirPeriod = 30*60*1000;//раз в полчаса
|
||||
const queue = new LimitedQueue(5, 100, 2*60*1000 + 15000);//2 минуты ожидание подвижек
|
||||
|
||||
let instance = null;
|
||||
@@ -33,15 +35,30 @@ class ReaderWorker {
|
||||
this.decomp = new FileDecompressor(3*config.maxUploadFileSize);
|
||||
this.bookConverter = new BookConverter(this.config);
|
||||
|
||||
this.remoteWebDavStorage = false;
|
||||
if (config.remoteWebDavStorage) {
|
||||
this.remoteWebDavStorage = new RemoteWebDavStorage(
|
||||
Object.assign({maxContentLength: 3*config.maxUploadFileSize}, config.remoteWebDavStorage)
|
||||
this.connManager = new JembaConnManager();
|
||||
this.appDb = this.connManager.db['app'];
|
||||
|
||||
this.remoteStorage = false;
|
||||
if (config.remoteStorage) {
|
||||
this.remoteStorage = new RemoteStorage(
|
||||
Object.assign({maxContentLength: 3*config.maxUploadFileSize}, config.remoteStorage)
|
||||
);
|
||||
}
|
||||
|
||||
this.periodicCleanDir(this.config.tempPublicDir, this.config.maxTempPublicDirSize, cleanDirPeriod);
|
||||
this.periodicCleanDir(this.config.uploadDir, this.config.maxUploadPublicDirSize, cleanDirPeriod);
|
||||
this.remoteConfig = {
|
||||
'/tmp': {
|
||||
dir: this.config.tempPublicDir,
|
||||
maxSize: this.config.maxTempPublicDirSize,
|
||||
moveToRemote: true,
|
||||
},
|
||||
'/upload': {
|
||||
dir: this.config.uploadDir,
|
||||
maxSize: this.config.maxUploadPublicDirSize,
|
||||
moveToRemote: true,
|
||||
}
|
||||
};
|
||||
|
||||
this.periodicCleanDir(this.remoteConfig);//no await
|
||||
|
||||
instance = this;
|
||||
}
|
||||
@@ -54,7 +71,6 @@ class ReaderWorker {
|
||||
let decompDir = '';
|
||||
let downloadedFilename = '';
|
||||
let isUploaded = false;
|
||||
let isRestored = false;
|
||||
let convertFilename = '';
|
||||
|
||||
const overLoadMes = 'Слишком большая очередь загрузки. Пожалуйста, попробуйте позже.';
|
||||
@@ -94,8 +110,7 @@ class ReaderWorker {
|
||||
if (!await fs.pathExists(downloadedFilename)) {
|
||||
//если удалено из upload, попробуем восстановить из удаленного хранилища
|
||||
try {
|
||||
downloadedFilename = await this.restoreRemoteFile(fileHash);
|
||||
isRestored = true;
|
||||
await this.restoreRemoteFile(fileHash, '/upload');
|
||||
} catch(e) {
|
||||
throw new Error('Файл не найден на сервере (возможно был удален как устаревший). Пожалуйста, загрузите файл с диска на сервер заново.');
|
||||
}
|
||||
@@ -144,33 +159,6 @@ class ReaderWorker {
|
||||
const finishFilename = path.basename(compFilename);
|
||||
wState.finish({path: `/tmp/${finishFilename}`, size: stat.size});
|
||||
|
||||
//лениво сохраним compFilename в удаленном хранилище
|
||||
if (this.remoteWebDavStorage) {
|
||||
(async() => {
|
||||
await utils.sleep(20*1000);
|
||||
try {
|
||||
//log(`remoteWebDavStorage.putFile ${path.basename(compFilename)}`);
|
||||
await this.remoteWebDavStorage.putFile(compFilename);
|
||||
} catch (e) {
|
||||
log(LM_ERR, e.stack);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
//лениво сохраним downloadedFilename в tmp и в удаленном хранилище в случае isUploaded
|
||||
if (this.remoteWebDavStorage && isUploaded && !isRestored) {
|
||||
(async() => {
|
||||
await utils.sleep(30*1000);
|
||||
try {
|
||||
//сжимаем файл в tmp, если там уже нет с тем же именем-sha256
|
||||
const compDownloadedFilename = await this.decomp.gzipFileIfNotExists(downloadedFilename, this.config.tempPublicDir, true);
|
||||
await this.remoteWebDavStorage.putFile(compDownloadedFilename);
|
||||
} catch (e) {
|
||||
log(LM_ERR, e.stack);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log(LM_ERR, e.stack);
|
||||
let mes = e.message.split('|FORLOG|');
|
||||
@@ -240,14 +228,20 @@ class ReaderWorker {
|
||||
return url;
|
||||
}
|
||||
|
||||
async restoreRemoteFile(filename) {
|
||||
async restoreRemoteFile(filename, remoteDir) {
|
||||
let targetDir = '';
|
||||
if (this.remoteConfig[remoteDir])
|
||||
targetDir = this.remoteConfig[remoteDir].dir;
|
||||
else
|
||||
throw new Error(`restoreRemoteFile: unknown remoteDir value (${remoteDir})`);
|
||||
|
||||
const basename = path.basename(filename);
|
||||
const targetName = `${this.config.tempPublicDir}/${basename}`;
|
||||
const targetName = `${targetDir}/${basename}`;
|
||||
|
||||
if (!await fs.pathExists(targetName)) {
|
||||
let found = false;
|
||||
if (this.remoteWebDavStorage) {
|
||||
found = await this.remoteWebDavStorage.getFileSuccess(targetName);
|
||||
if (this.remoteStorage) {
|
||||
found = await this.remoteStorage.getFileSuccess(targetName, remoteDir);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
@@ -258,83 +252,110 @@ class ReaderWorker {
|
||||
return targetName;
|
||||
}
|
||||
|
||||
restoreCachedFile(filename) {
|
||||
const workerId = this.workerState.generateWorkerId();
|
||||
const wState = this.workerState.getControl(workerId);
|
||||
wState.set({state: 'start'});
|
||||
async cleanDir(dir, remoteDir, maxSize, moveToRemote) {
|
||||
const sent = this.remoteSent;
|
||||
|
||||
(async() => {
|
||||
try {
|
||||
wState.set({state: 'download', step: 1, totalSteps: 1, path: filename, progress: 0});
|
||||
const list = await fs.readdir(dir);
|
||||
|
||||
const targetName = await this.restoreRemoteFile(filename);
|
||||
const stat = await fs.stat(targetName);
|
||||
|
||||
const basename = path.basename(filename);
|
||||
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});
|
||||
let size = 0;
|
||||
let files = [];
|
||||
for (const filename of list) {
|
||||
const filePath = `${dir}/${filename}`;
|
||||
const stat = await fs.stat(filePath);
|
||||
if (!stat.isDirectory()) {
|
||||
size += stat.size;
|
||||
files.push({name: filePath, stat});
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
return workerId;
|
||||
}
|
||||
log(`clean dir ${dir}, maxSize=${maxSize}, found ${files.length} files, total size=${size}`);
|
||||
|
||||
async periodicCleanDir(dir, maxSize, timeout) {
|
||||
try {
|
||||
const list = await fs.readdir(dir);
|
||||
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
||||
|
||||
let size = 0;
|
||||
let files = [];
|
||||
for (const name of list) {
|
||||
const stat = await fs.stat(`${dir}/${name}`);
|
||||
if (!stat.isDirectory()) {
|
||||
size += stat.size;
|
||||
files.push({name, stat});
|
||||
//удаленное хранилище
|
||||
if (moveToRemote && this.remoteStorage) {
|
||||
const foundFiles = new Set();
|
||||
for (const file of files) {
|
||||
foundFiles.add(file.name);
|
||||
|
||||
if (sent[file.name])
|
||||
continue;
|
||||
|
||||
//отправляем в remoteStorage
|
||||
try {
|
||||
log(`remoteStorage.putFile ${remoteDir}/${path.basename(file.name)}`);
|
||||
await this.remoteStorage.putFile(file.name, remoteDir);
|
||||
|
||||
sent[file.name] = true;
|
||||
await this.appDb.insert({table: 'remote_sent', ignore: true, rows: [{id: file.name, remoteDir}]});
|
||||
} catch (e) {
|
||||
log(LM_ERR, e.stack);
|
||||
}
|
||||
}
|
||||
log(`clean dir ${dir}, maxSize=${maxSize}, found ${files.length} files, total size=${size}`);
|
||||
|
||||
files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
|
||||
//почистим remoteSent и БД
|
||||
//несколько неоптимально, таскает все записи из БД
|
||||
const rows = await this.appDb.select({table: 'remote_sent'});
|
||||
for (const row of rows) {
|
||||
if (row.remoteDir === remoteDir && !foundFiles.has(row.id)) {
|
||||
delete sent[row.id];
|
||||
await this.appDb.delete({table: 'remote_sent', where: `@@id(${this.appDb.esc(row.id)})`});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
while (i < files.length && size > maxSize) {
|
||||
const file = files[i];
|
||||
const oldFile = `${dir}/${file.name}`;
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
while (i < files.length && size > maxSize) {
|
||||
const file = files[i];
|
||||
const oldFile = file.name;
|
||||
|
||||
let remoteSuccess = true;
|
||||
//отправляем только this.config.tempPublicDir
|
||||
if (this.remoteWebDavStorage && dir === this.config.tempPublicDir) {
|
||||
remoteSuccess = false;
|
||||
//реально удаляем только если сохранили в хранилище или размер dir увеличен в 1.5 раза
|
||||
if (!(moveToRemote && this.remoteStorage)
|
||||
|| (moveToRemote && this.remoteStorage && sent[oldFile])
|
||||
|| size > maxSize*1.5) {
|
||||
await fs.remove(oldFile);
|
||||
j++;
|
||||
}
|
||||
|
||||
size -= file.stat.size;
|
||||
i++;
|
||||
}
|
||||
|
||||
log(`removed ${j} files`);
|
||||
}
|
||||
|
||||
async periodicCleanDir(cleanConfig) {
|
||||
try {
|
||||
if (!this.remoteSent)
|
||||
this.remoteSent = {};
|
||||
|
||||
//инициализация this.remoteSent
|
||||
if (this.remoteStorage) {
|
||||
const rows = await this.appDb.select({table: 'remote_sent'});
|
||||
for (const row of rows) {
|
||||
this.remoteSent[row.id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (1) {// eslint-disable-line no-constant-condition
|
||||
|
||||
for (const [remoteDir, config] of Object.entries(cleanConfig)) {
|
||||
try {
|
||||
//log(`remoteWebDavStorage.putFile ${path.basename(oldFile)}`);
|
||||
await this.remoteWebDavStorage.putFile(oldFile);
|
||||
remoteSuccess = true;
|
||||
} catch (e) {
|
||||
await this.cleanDir(config.dir, remoteDir, config.maxSize, config.moveToRemote);
|
||||
} catch(e) {
|
||||
log(LM_ERR, e.stack);
|
||||
}
|
||||
}
|
||||
//реально удаляем только если сохранили в хранилище
|
||||
if (remoteSuccess || size > maxSize*1.2) {
|
||||
await fs.remove(oldFile);
|
||||
j++;
|
||||
}
|
||||
|
||||
size -= file.stat.size;
|
||||
i++;
|
||||
|
||||
await utils.sleep(cleanDirPeriod);
|
||||
}
|
||||
log(`removed ${j} files`);
|
||||
} catch(e) {
|
||||
log(LM_ERR, e.stack);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.periodicCleanDir(dir, maxSize, timeout);
|
||||
}, timeout);
|
||||
} catch (e) {
|
||||
log(LM_FATAL, e.message);
|
||||
ayncExit.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ReaderWorker;
|
||||
98
server/core/RemoteStorage.js
Normal file
98
server/core/RemoteStorage.js
Normal file
@@ -0,0 +1,98 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const WebSocketConnection = require('./WebSocketConnection');
|
||||
|
||||
class RemoteStorage {
|
||||
constructor(config) {
|
||||
this.config = Object.assign({}, config);
|
||||
this.config.maxContentLength = this.config.maxContentLength || 10*1024*1024;
|
||||
|
||||
this.accessToken = this.config.accessToken;
|
||||
|
||||
this.wsc = new WebSocketConnection(config.url);
|
||||
}
|
||||
|
||||
async wsRequest(query) {
|
||||
const response = await this.wsc.message(
|
||||
await this.wsc.send(Object.assign({accessToken: this.accessToken}, query), 600),
|
||||
600
|
||||
);
|
||||
if (response.error)
|
||||
throw new Error(response.error);
|
||||
return response;
|
||||
}
|
||||
|
||||
async wsStat(fileName) {
|
||||
return await this.wsRequest({action: 'get-stat', fileName});
|
||||
}
|
||||
|
||||
async wsGetFile(fileName) {
|
||||
return this.wsRequest({action: 'get-file', fileName});
|
||||
}
|
||||
|
||||
async wsPutFile(fileName, data) {//data base64 encoded string
|
||||
return this.wsRequest({action: 'put-file', fileName, data});
|
||||
}
|
||||
|
||||
async wsDelFile(fileName) {
|
||||
return this.wsRequest({action: 'del-file', fileName});
|
||||
}
|
||||
|
||||
makeRemoteFileName(fileName, dir = '') {
|
||||
const base = path.basename(fileName);
|
||||
if (base.length > 3) {
|
||||
return `${dir}/${base.substr(0, 3)}/${base}`;
|
||||
} else {
|
||||
return `${dir}/${base}`;
|
||||
}
|
||||
}
|
||||
|
||||
async putFile(fileName, dir = '') {
|
||||
if (!await fs.pathExists(fileName)) {
|
||||
throw new Error(`File not found: ${fileName}`);
|
||||
}
|
||||
|
||||
const remoteFilename = this.makeRemoteFileName(fileName, dir);
|
||||
|
||||
try {
|
||||
const localStat = await fs.stat(fileName);
|
||||
let remoteStat = await this.wsStat(remoteFilename);
|
||||
remoteStat = remoteStat.stat;
|
||||
|
||||
if (remoteStat.isFile && localStat.size == remoteStat.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.wsDelFile(remoteFilename);
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
|
||||
const data = await fs.readFile(fileName, 'base64');
|
||||
await this.wsPutFile(remoteFilename, data);
|
||||
}
|
||||
|
||||
async getFile(fileName, dir = '') {
|
||||
if (await fs.pathExists(fileName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const remoteFilename = this.makeRemoteFileName(fileName, dir);
|
||||
|
||||
const response = await this.wsGetFile(remoteFilename);
|
||||
await fs.writeFile(fileName, response.data, 'base64');
|
||||
}
|
||||
|
||||
async getFileSuccess(filename, dir = '') {
|
||||
try {
|
||||
await this.getFile(filename, dir);
|
||||
return true;
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RemoteStorage;
|
||||
@@ -46,16 +46,16 @@ class RemoteWebDavStorage {
|
||||
return await this.wdc.createDirectory(dirname);
|
||||
}
|
||||
|
||||
async putFile(filename) {
|
||||
async putFile(filename, dir = '') {
|
||||
if (!await fs.pathExists(filename)) {
|
||||
throw new Error(`File not found: ${filename}`);
|
||||
}
|
||||
|
||||
const base = path.basename(filename);
|
||||
let remoteFilename = `/${base}`;
|
||||
let remoteFilename = `${dir}/${base}`;
|
||||
|
||||
if (base.length > 3) {
|
||||
const remoteDir = `/${base.substr(0, 3)}`;
|
||||
const remoteDir = `${dir}/${base.substr(0, 3)}`;
|
||||
try {
|
||||
await this.mkdir(remoteDir);
|
||||
} catch (e) {
|
||||
@@ -79,24 +79,24 @@ class RemoteWebDavStorage {
|
||||
await this.writeFile(remoteFilename, data);
|
||||
}
|
||||
|
||||
async getFile(filename) {
|
||||
async getFile(filename, dir = '') {
|
||||
if (await fs.pathExists(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const base = path.basename(filename);
|
||||
let remoteFilename = `/${base}`;
|
||||
let remoteFilename = `${dir}/${base}`;
|
||||
if (base.length > 3) {
|
||||
remoteFilename = `/${base.substr(0, 3)}/${base}`;
|
||||
remoteFilename = `${dir}/${base.substr(0, 3)}/${base}`;
|
||||
}
|
||||
|
||||
const data = await this.readFile(remoteFilename);
|
||||
await fs.writeFile(filename, data);
|
||||
}
|
||||
|
||||
async getFileSuccess(filename) {
|
||||
async getFileSuccess(filename, dir = '') {
|
||||
try {
|
||||
await this.getFile(filename);
|
||||
await this.getFile(filename, dir);
|
||||
return true;
|
||||
} catch (e) {
|
||||
//
|
||||
|
||||
12
server/db/jembaMigrations/app/001-create.js
Normal file
12
server/db/jembaMigrations/app/001-create.js
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
up: [
|
||||
['create', {
|
||||
table: 'remote_sent'
|
||||
}],
|
||||
],
|
||||
down: [
|
||||
['drop', {
|
||||
table: 'remote_sent'
|
||||
}],
|
||||
]
|
||||
};
|
||||
6
server/db/jembaMigrations/app/index.js
Normal file
6
server/db/jembaMigrations/app/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
table: 'migration1',
|
||||
data: [
|
||||
{id: 1, name: 'create', data: require('./001-create')}
|
||||
]
|
||||
}
|
||||
16
server/db/jembaMigrations/book-update-server/001-create.js
Normal file
16
server/db/jembaMigrations/book-update-server/001-create.js
Normal file
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
up: [
|
||||
['create', {
|
||||
table: 'checked',
|
||||
index: [
|
||||
{field: 'queryTime', type: 'number'},
|
||||
{field: 'checkTime', type: 'number'},
|
||||
]
|
||||
}],
|
||||
],
|
||||
down: [
|
||||
['drop', {
|
||||
table: 'checked'
|
||||
}],
|
||||
]
|
||||
};
|
||||
6
server/db/jembaMigrations/book-update-server/index.js
Normal file
6
server/db/jembaMigrations/book-update-server/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
table: 'migration1',
|
||||
data: [
|
||||
{id: 1, name: 'create', data: require('./001-create')}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
module.exports = {
|
||||
//'app': require('./jembaMigrations/app'),
|
||||
'app': require('./app'),
|
||||
'reader-storage': require('./reader-storage'),
|
||||
'book-update-server': require('./book-update-server'),
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require('tls').DEFAULT_MIN_VERSION = 'TLSv1';
|
||||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
const express = require('express');
|
||||
const compression = require('compression');
|
||||
@@ -81,16 +82,6 @@ async function main() {
|
||||
if (devModule)
|
||||
devModule.logQueries(app);
|
||||
|
||||
app.use(express.static(serverConfig.publicDir, {
|
||||
maxAge: '30d',
|
||||
setHeaders: (res, filePath) => {
|
||||
if (path.basename(path.dirname(filePath)) == 'tmp') {
|
||||
res.set('Content-Type', 'application/xml');
|
||||
res.set('Content-Encoding', 'gzip');
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
require('./routes').initRoutes(app, wss, serverConfig);
|
||||
|
||||
if (devModule) {
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
const c = require('./controllers');
|
||||
const utils = require('./core/utils');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
|
||||
const ReaderWorker = require('./core/Reader/ReaderWorker');//singleton
|
||||
const log = new (require('./core/AppLogger'))().log;//singleton
|
||||
|
||||
const c = require('./controllers');
|
||||
const utils = require('./core/utils');
|
||||
|
||||
function initRoutes(app, wss, config) {
|
||||
//эксклюзив для update_checker
|
||||
if (config.mode === 'book_update_checker') {
|
||||
new c.BookUpdateCheckerController(wss, config);
|
||||
return;
|
||||
}
|
||||
|
||||
initStatic(app, config);
|
||||
|
||||
const misc = new c.MiscController(config);
|
||||
const reader = new c.ReaderController(config);
|
||||
const worker = new c.WorkerController(config);
|
||||
@@ -29,7 +45,6 @@ function initRoutes(app, wss, config) {
|
||||
['POST', '/api/reader/load-book', reader.loadBook.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/restore-cached-file', reader.restoreCachedFile.bind(reader), [aAll], {}],
|
||||
['POST', '/api/worker/get-state', worker.getState.bind(worker), [aAll], {}],
|
||||
];
|
||||
|
||||
@@ -77,6 +92,48 @@ function initRoutes(app, wss, config) {
|
||||
}
|
||||
}
|
||||
|
||||
function initStatic(app, config) {
|
||||
const readerWorker = new ReaderWorker(config);
|
||||
|
||||
//восстановление файлов в /tmp и /upload из webdav-storage, при необходимости
|
||||
app.use(async(req, res, next) => {
|
||||
if ((req.method !== 'GET' && req.method !== 'HEAD') ||
|
||||
!(req.path.indexOf('/tmp/') === 0 || req.path.indexOf('/upload/') === 0)
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const filePath = `${config.publicDir}${req.path}`;
|
||||
|
||||
//восстановим
|
||||
try {
|
||||
if (!await fs.pathExists(filePath)) {
|
||||
if (req.path.indexOf('/tmp/') === 0) {
|
||||
await readerWorker.restoreRemoteFile(req.path, '/tmp');
|
||||
} else if (req.path.indexOf('/upload/') === 0) {
|
||||
await readerWorker.restoreRemoteFile(req.path, '/upload');
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
log(LM_ERR, `Static.restoreRemoteFile: ${e.message}`);
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
const tmpDir = `${config.publicDir}/tmp`;
|
||||
app.use(express.static(config.publicDir, {
|
||||
maxAge: '30d',
|
||||
|
||||
setHeaders: (res, filePath) => {
|
||||
if (path.dirname(filePath) == tmpDir) {
|
||||
res.set('Content-Type', 'application/xml');
|
||||
res.set('Content-Encoding', 'gzip');
|
||||
}
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initRoutes
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user