Răsfoiți Sursa

Merge branch 'release/0.11.8-2'

Book Pauk 3 ani în urmă
părinte
comite
451538fcf7

+ 1 - 26
client/api/reader.js

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

+ 15 - 12
client/components/Reader/SetPositionPage/SetPositionPage.vue

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

+ 25 - 4
docs/liberama.top/liberama

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

+ 12 - 2
docs/omnireader.ru/omnireader

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

+ 12 - 2
docs/omnireader.ru/omnireader_http

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

+ 1 - 1
server/config/base.js

@@ -49,7 +49,7 @@ 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',
         },

+ 95 - 0
server/controllers/BookUpdateCheckerController.js

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

+ 0 - 18
server/controllers/ReaderController.js

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

+ 0 - 11
server/controllers/WebSocketController.js

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

+ 1 - 0
server/controllers/index.js

@@ -3,4 +3,5 @@ module.exports = {
     ReaderController: require('./ReaderController'),
     WorkerController: require('./WorkerController'),
     WebSocketController: require('./WebSocketController'),
+    BookUpdateCheckerController: require('./BookUpdateCheckerController'),
 }

+ 0 - 0
server/core/BookUpdateChecker/BUCClient.js


+ 24 - 0
server/core/BookUpdateChecker/BUCServer.js

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

+ 1 - 1
server/core/FileDownloader.js

@@ -23,7 +23,7 @@ class FileDownloader {
                 estSize = res.headers['content-length'];
             }
 
-            if (estSize > this.limitDownloadSize) {
+            if (this.limitDownloadSize && estSize > this.limitDownloadSize) {
                 throw new Error('Файл слишком большой');
             }
 

+ 82 - 98
server/core/Reader/ReaderWorker.js

@@ -11,7 +11,7 @@ const RemoteWebDavStorage = require('../RemoteWebDavStorage');
 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;
@@ -40,8 +40,20 @@ class ReaderWorker {
                 );
             }
 
-            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 +66,6 @@ class ReaderWorker {
         let decompDir = '';
         let downloadedFilename = '';
         let isUploaded = false;
-        let isRestored = false;
         let convertFilename = '';
 
         const overLoadMes = 'Слишком большая очередь загрузки. Пожалуйста, попробуйте позже.';
@@ -94,8 +105,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 +154,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 +223,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);
+                found = await this.remoteWebDavStorage.getFileSuccess(targetName, remoteDir);
             }
 
             if (!found) {
@@ -258,83 +247,78 @@ 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) {
+        if (!this.remoteSent)
+            this.remoteSent = {};
+        if (!this.remoteSent[remoteDir])
+            this.remoteSent[remoteDir] = {};
 
-        (async() => {
-            try {
-                wState.set({state: 'download', step: 1, totalSteps: 1, path: filename, progress: 0});
+        const sent = this.remoteSent[remoteDir];
 
-                const targetName = await this.restoreRemoteFile(filename);
-                const stat = await fs.stat(targetName);
+        const list = await fs.readdir(dir);
 
-                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});
             }
-        })();
+        }
+        log(`clean dir ${dir}, maxSize=${maxSize}, found ${files.length} files, total size=${size}`);
 
-        return workerId;
-    }
+        files.sort((a, b) => a.stat.mtimeMs - b.stat.mtimeMs);
 
-    async periodicCleanDir(dir, maxSize, timeout) {
-        try {
-            const list = await fs.readdir(dir);
-
-            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.remoteWebDavStorage) {
+            for (const file of files) {
+                if (sent[file.name])
+                    continue;
+
+                //отправляем в remoteWebDavStorage
+                try {
+                    log(`remoteWebDavStorage.putFile ${remoteDir}/${path.basename(file.name)}`);
+                    await this.remoteWebDavStorage.putFile(file.name, remoteDir);
+                    sent[file.name] = true;
+                } 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);
+        let i = 0;
+        let j = 0;
+        while (i < files.length && size > maxSize) {
+            const file = files[i];
+            const oldFile = file.name;
 
-            let i = 0;
-            let j = 0;
-            while (i < files.length && size > maxSize) {
-                const file = files[i];
-                const oldFile = `${dir}/${file.name}`;
+            //реально удаляем только если сохранили в хранилище или размер dir увеличен в 1.5 раза
+            if ((moveToRemote && this.remoteWebDavStorage && sent[oldFile]) || size > maxSize*1.5) {
+                await fs.remove(oldFile);
+                j++;
+            }
+            
+            size -= file.stat.size;
+            i++;
+        }
+        log(`removed ${j} files`);
+    }
 
-                let remoteSuccess = true;
-                //отправляем только this.config.tempPublicDir
-                if (this.remoteWebDavStorage && dir === this.config.tempPublicDir) {
-                    remoteSuccess = false;
-                    try {
-                        //log(`remoteWebDavStorage.putFile ${path.basename(oldFile)}`);
-                        await this.remoteWebDavStorage.putFile(oldFile);
-                        remoteSuccess = true;
-                    } catch (e) {
-                        log(LM_ERR, e.stack);
-                    }
-                }
-                //реально удаляем только если сохранили в хранилище
-                if (remoteSuccess || size > maxSize*1.2) {
-                    await fs.remove(oldFile);
-                    j++;
+    async periodicCleanDir(cleanConfig) {
+        while (1) {// eslint-disable-line no-constant-condition
+            for (const [remoteDir, config] of Object.entries(cleanConfig)) {
+                try {
+                    await this.cleanDir(config.dir, remoteDir, config.maxSize, config.moveToRemote);
+                } catch(e) {
+                    log(LM_ERR, e.stack);
                 }
-                
-                size -= file.stat.size;
-                i++;
             }
-            log(`removed ${j} files`);
-        } catch(e) {
-            log(LM_ERR, e.stack);
-        } finally {
-            setTimeout(() => {
-                this.periodicCleanDir(dir, maxSize, timeout);
-            }, timeout);
+
+            await utils.sleep(cleanDirPeriod);
         }
     }
+
 }
 
 module.exports = ReaderWorker;

+ 8 - 8
server/core/RemoteWebDavStorage.js

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

+ 16 - 0
server/db/jembaMigrations/book-update-server/001-create.js

@@ -0,0 +1,16 @@
+module.exports = {
+    up: [
+        ['create', {
+            table: 'checked',
+            index: [
+                {field: 'queryTime', type: 'number'},
+                {field: 'checkTime', type: 'number'},
+            ]
+        }],
+    ],    
+    down: [
+        ['drop', {
+            table: 'checked'
+        }],
+    ]
+};

+ 6 - 0
server/db/jembaMigrations/book-update-server/index.js

@@ -0,0 +1,6 @@
+module.exports = {
+    table: 'migration1',
+    data: [
+        {id: 1, name: 'create', data: require('./001-create')}
+    ]
+}

+ 1 - 1
server/db/jembaMigrations/index.js

@@ -1,4 +1,4 @@
 module.exports = {
-    //'app': require('./jembaMigrations/app'),
     'reader-storage': require('./reader-storage'),
+    'book-update-server': require('./book-update-server'),
 };

+ 2 - 11
server/index.js

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

+ 59 - 2
server/routes.js

@@ -1,8 +1,24 @@
+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');
-const multer = require('multer');
 
 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
 }