|
@@ -1,14 +1,19 @@
|
|
|
const fs = require('fs-extra');
|
|
|
const path = require('path');
|
|
|
|
|
|
+const LimitedQueue = require('../LimitedQueue');
|
|
|
const WorkerState = require('../WorkerState');//singleton
|
|
|
const FileDownloader = require('../FileDownloader');
|
|
|
const FileDecompressor = require('../FileDecompressor');
|
|
|
const BookConverter = require('./BookConverter');
|
|
|
+const RemoteWebDavStorage = require('../RemoteWebDavStorage');
|
|
|
|
|
|
const utils = require('../utils');
|
|
|
const log = new (require('../AppLogger'))().log;//singleton
|
|
|
|
|
|
+const cleanDirPeriod = 60*60*1000;//1 раз в час
|
|
|
+const queue = new LimitedQueue(5, 100, 5*60*1000);//5 минут ожидание подвижек
|
|
|
+
|
|
|
let instance = null;
|
|
|
|
|
|
//singleton
|
|
@@ -24,12 +29,19 @@ class ReaderWorker {
|
|
|
fs.ensureDirSync(this.config.tempPublicDir);
|
|
|
|
|
|
this.workerState = new WorkerState();
|
|
|
- this.down = new FileDownloader();
|
|
|
- this.decomp = new FileDecompressor();
|
|
|
+ this.down = new FileDownloader(config.maxUploadFileSize);
|
|
|
+ this.decomp = new FileDecompressor(2*config.maxUploadFileSize);
|
|
|
this.bookConverter = new BookConverter(this.config);
|
|
|
|
|
|
- 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.remoteWebDavStorage = false;
|
|
|
+ if (config.remoteWebDavStorage) {
|
|
|
+ this.remoteWebDavStorage = new RemoteWebDavStorage(
|
|
|
+ Object.assign({maxContentLength: config.maxUploadFileSize}, config.remoteWebDavStorage)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ this.periodicCleanDir(this.config.tempPublicDir, this.config.maxTempPublicDirSize, cleanDirPeriod);
|
|
|
+ this.periodicCleanDir(this.config.uploadDir, this.config.maxUploadPublicDirSize, cleanDirPeriod);
|
|
|
|
|
|
instance = this;
|
|
|
}
|
|
@@ -39,22 +51,39 @@ class ReaderWorker {
|
|
|
|
|
|
async loadBook(opts, wState) {
|
|
|
const url = opts.url;
|
|
|
- let errMes = '';
|
|
|
let decompDir = '';
|
|
|
let downloadedFilename = '';
|
|
|
let isUploaded = false;
|
|
|
let convertFilename = '';
|
|
|
+
|
|
|
+ const overLoadMes = 'Слишком большая очередь загрузки. Пожалуйста, попробуйте позже.';
|
|
|
+ const overLoadErr = new Error(overLoadMes);
|
|
|
+
|
|
|
+ let q = null;
|
|
|
try {
|
|
|
+ wState.set({state: 'queue', step: 1, totalSteps: 1});
|
|
|
+ try {
|
|
|
+ let qSize = 0;
|
|
|
+ q = await queue.get((place) => {
|
|
|
+ wState.set({place, progress: (qSize ? Math.round((qSize - place)/qSize*100) : 0)});
|
|
|
+ if (!qSize)
|
|
|
+ qSize = place;
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ throw overLoadErr;
|
|
|
+ }
|
|
|
+
|
|
|
wState.set({state: 'download', step: 1, totalSteps: 3, url});
|
|
|
|
|
|
const tempFilename = utils.randomHexString(30);
|
|
|
const tempFilename2 = utils.randomHexString(30);
|
|
|
const decompDirname = utils.randomHexString(30);
|
|
|
|
|
|
+ //download or use uploaded
|
|
|
if (url.indexOf('file://') != 0) {//download
|
|
|
const downdata = await this.down.load(url, (progress) => {
|
|
|
wState.set({progress});
|
|
|
- });
|
|
|
+ }, q.abort);
|
|
|
|
|
|
downloadedFilename = `${this.config.tempDownloadDir}/${tempFilename}`;
|
|
|
await fs.writeFile(downloadedFilename, downdata);
|
|
@@ -67,6 +96,10 @@ class ReaderWorker {
|
|
|
}
|
|
|
wState.set({progress: 100});
|
|
|
|
|
|
+ if (q.abort())
|
|
|
+ throw overLoadErr;
|
|
|
+ q.resetTimeout();
|
|
|
+
|
|
|
//decompress
|
|
|
wState.set({state: 'decompress', step: 2, progress: 0});
|
|
|
decompDir = `${this.config.tempDownloadDir}/${decompDirname}`;
|
|
@@ -79,27 +112,49 @@ class ReaderWorker {
|
|
|
}
|
|
|
wState.set({progress: 100});
|
|
|
|
|
|
+ if (q.abort())
|
|
|
+ throw overLoadErr;
|
|
|
+ q.resetTimeout();
|
|
|
+
|
|
|
//конвертирование в fb2
|
|
|
wState.set({state: 'convert', step: 3, progress: 0});
|
|
|
convertFilename = `${this.config.tempDownloadDir}/${tempFilename2}`;
|
|
|
await this.bookConverter.convertToFb2(decompFiles, convertFilename, opts, progress => {
|
|
|
wState.set({progress});
|
|
|
- });
|
|
|
+ }, q.abort);
|
|
|
|
|
|
//сжимаем файл в 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});
|
|
|
|
|
|
//finish
|
|
|
const finishFilename = path.basename(compFilename);
|
|
|
- wState.finish({path: `/tmp/${finishFilename}`});
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ })();
|
|
|
+ }
|
|
|
|
|
|
} catch (e) {
|
|
|
log(LM_ERR, e.stack);
|
|
|
- wState.set({state: 'error', error: (errMes ? errMes : e.message)});
|
|
|
+ if (e.message == 'abort')
|
|
|
+ e.message = overLoadMes;
|
|
|
+ wState.set({state: 'error', error: e.message});
|
|
|
} finally {
|
|
|
//clean
|
|
|
+ if (q)
|
|
|
+ q.ret();
|
|
|
if (decompDir)
|
|
|
await fs.remove(decompDir);
|
|
|
if (downloadedFilename && !isUploaded)
|
|
@@ -133,6 +188,41 @@ class ReaderWorker {
|
|
|
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) {
|
|
|
try {
|
|
|
const list = await fs.readdir(dir);
|
|
@@ -153,7 +243,19 @@ class ReaderWorker {
|
|
|
let i = 0;
|
|
|
while (i < files.length && size > maxSize) {
|
|
|
const file = files[i];
|
|
|
- await fs.remove(`${dir}/${file.name}`);
|
|
|
+ const oldFile = `${dir}/${file.name}`;
|
|
|
+
|
|
|
+ //отправляем только this.config.tempPublicDir
|
|
|
+ //TODO: убрать в будущем, т.к. уже делается ленивое сохранение compFilename в удаленном хранилище
|
|
|
+ if (this.remoteWebDavStorage && dir === this.config.tempPublicDir) {
|
|
|
+ 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;
|
|
|
i++;
|
|
|
}
|