Files
liberama/server/core/BookUpdateChecker/BUCClient.js
2022-09-01 15:31:16 +07:00

262 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const WebSocketConnection = require('../WebSocketConnection');
const JembaConnManager = require('../../db/JembaConnManager');//singleton
const ayncExit = new (require('../AsyncExit'))();
const utils = require('../utils');
const log = new (require('../AppLogger'))().log;//singleton
const minuteMs = 60*1000;
const hourMs = 60*minuteMs;
const dayMs = 24*hourMs;
let instance = null;
//singleton
class BUCClient {
constructor(config) {
if (!instance) {
this.config = config;
this.connManager = new JembaConnManager();
this.appDb = this.connManager.db['app'];
this.wsc = new WebSocketConnection(config.bucServer.url, 10, 30, {rejectUnauthorized: false});
this.accessToken = config.bucServer.accessToken;
//константы
if (this.config.branch !== 'development') {
this.cleanQueryInterval = 300*dayMs;//интервал очистки устаревших
this.syncPeriod = 1*hourMs;//период синхронизации с сервером BUC
this.sendBookUrlsPeriod = 1*minuteMs;//период отправки BookUrls на сервер BUC
} else {
this.cleanQueryInterval = 300*dayMs;//интервал очистки устаревших
this.syncPeriod = 1*minuteMs;//период синхронизации с сервером BUC
this.sendBookUrlsPeriod = 1*1000;//период отправки BookUrls на сервер BUC
}
this.fromCheckTime = 1;
this.bookUrls = new Set();
this.main();//no await
instance = this;
}
return instance;
}
async wsRequest(query) {
const response = await this.wsc.message(
await this.wsc.send(Object.assign({accessToken: this.accessToken}, query), 60),
60
);
if (response.error)
throw new Error(response.error);
return response;
}
async wsGetBuc(fromCheckTime, callback) {
const requestId = await this.wsc.send({accessToken: this.accessToken, action: 'get-buc', fromCheckTime}, 60);
while (1) {//eslint-disable-line
const res = await this.wsc.message(requestId, 60);
if (res.state == 'get') {
await callback(res.rows);
} else {
break;
}
}
}
async wsUpdateBuc(bookUrls) {
return await this.wsRequest({action: 'update-buc', bookUrls});
}
async checkBuc(bookUrls) {
const db = this.appDb;
for (const url of bookUrls)
this.bookUrls.add(url);
const rows = await db.select({
table: 'buc',
map: `(r) => ({id: r.id, size: r.size})`,
where: `@@id(${db.esc(bookUrls)})`,
});
return rows;
}
async findMaxCheckTime() {
const db = this.appDb;
let result = 1;
//одним куском, возможно будет жрать память
const rows = await db.select({
table: 'buc',
where: `
const result = new Set();
let max = 0;
let maxId = null;
@iter(@all(), (row) => {
if (row.checkTime > max) {
max = row.checkTime;
maxId = row.id;
}
});
if (maxId)
result.add(maxId);
return result;
`
});
if (rows.length)
result = rows[0].checkTime;
return result;
}
async periodicSendBookUrls() {
while (1) {//eslint-disable-line
try {
//отправим this.bookUrls
if (this.bookUrls.size) {
log(`client: remote update buc begin`);
const arr = Array.from(this.bookUrls);
this.bookUrls = new Set();
const chunkSize = 100;
let updated = 0;
for (let i = 0; i < arr.length; i += chunkSize) {
const chunk = arr.slice(i, i + chunkSize);
const res = await this.wsUpdateBuc(chunk);
if (!res.error && res.state == 'success') {
//update success
updated += chunk.length;
} else {
for (const url of chunk) {
this.bookUrls.add(url);
}
log(LM_ERR, `update-buc error: ${(res.error ? res.error : `wrong state "${res.state}"`)}`);
}
}
log(`client: remote update buc end, updated ${updated} urls`);
}
} catch (e) {
log(LM_ERR, e.stack);
}
await utils.sleep(this.sendBookUrlsPeriod);
}
}
async periodicSync() {
const db = this.appDb;
while (1) {//eslint-disable-line
try {
//почистим нашу таблицу 'buc'
log(`client: clean 'buc' table begin`);
const cleanTime = Date.now() - this.cleanQueryInterval;
while (1) {//eslint-disable-line
//выборка всех по кусочкам
const rows = await db.select({
table: 'buc',
where: `
let iter = @getItem('clean');
if (!iter) {
iter = @all();
@setItem('clean', iter);
}
const ids = new Set();
let id = iter.next();
while (!id.done) {
ids.add(id.value);
if (ids.size >= 1000)
break;
id = iter.next();
}
return ids;
`
});
if (rows.length) {
const toDelIds = [];
for (const row of rows)
if (row.queryTime <= cleanTime)
toDelIds.push(row.id);
//удаление
const res = await db.delete({
table: 'buc',
where: `@@id(${db.esc(toDelIds)})`,
});
log(`client: clean 'buc' deleted ${res.deleted}`);
} else {
break;
}
}
await db.select({
table: 'buc',
where: `
@delItem('clean');
return new Set();
`
});
log(`client: clean 'buc' table end`);
//синхронизация с сервером BUC
log(`client: sync 'buc' table begin`);
this.fromCheckTime -= 30*minuteMs;//минус полчаса на всякий случай
await this.wsGetBuc(this.fromCheckTime, async(rows) => {
for (const row of rows) {
if (row.checkTime > this.fromCheckTime)
this.fromCheckTime = row.checkTime;
}
const res = await db.insert({
table: 'buc',
replace: true,
rows
});
log(`client: sync 'buc' table, inserted ${res.inserted} rows, replaced ${res.replaced}`);
});
log(`client: sync 'buc' table end`);
} catch (e) {
log(LM_ERR, e.stack);
}
await utils.sleep(this.syncPeriod);
}
}
async main() {
try {
if (!this.config.bucEnabled)
throw new Error('BookUpdateChecker disabled');
this.fromCheckTime = await this.findMaxCheckTime();
this.periodicSendBookUrls();//no await
this.periodicSync();//no await
log(`BUC Client Worker started`);
} catch (e) {
log(LM_FATAL, e.stack);
ayncExit.exit(1);
}
}
}
module.exports = BUCClient;