exchangemarket/src/backup/sync.js
2022-02-17 23:52:31 +05:30

268 lines
9.7 KiB
JavaScript

'use strict';
const {
HASH_N_ROW
} = require("../_constants")["backup"];
var DB; //Container for database
//Backup Transfer
function sendBackupData(timestamp, checksum, ws) {
if (!timestamp) timestamp = 0;
else if (typeof timestamp === "string" && /\.\d{3}Z$/.test(timestamp))
timestamp = timestamp.substring(0, timestamp.length - 1);
let promises = [
backupSync_data(timestamp, ws),
backupSync_delete(timestamp, ws)
];
if (checksum)
promises.push(backupSync_checksum(ws));
Promise.allSettled(promises).then(result => {
let failedSync = [];
result.forEach(r => r.status === "rejected" ? failedSync.push(r.reason) : null);
if (failedSync.length) {
console.info("Backup Sync Failed:", failedSync);
ws.send(JSON.stringify({
command: "SYNC_END",
status: false,
info: failedSync
}));
} else {
console.info("Backup Sync completed");
ws.send(JSON.stringify({
command: "SYNC_END",
status: true
}));
}
});
}
function backupSync_delete(timestamp, ws) {
return new Promise((resolve, reject) => {
DB.query("SELECT * FROM _backup WHERE mode is NULL AND timestamp > ?", [timestamp]).then(result => {
ws.send(JSON.stringify({
command: "SYNC_DELETE",
delete_data: result
}));
resolve("deleteSync");
}).catch(error => {
console.error(error);
reject("deleteSync");
});
})
}
function backupSync_data(timestamp, ws) {
const sendTable = (table, id_list) => new Promise((res, rej) => {
DB.query(`SELECT * FROM ${table} WHERE id IN (${id_list})`)
.then(data => {
ws.send(JSON.stringify({
table,
command: "SYNC_UPDATE",
data
}));
res(table);
}).catch(error => {
console.error(error);
rej(table);
});
});
return new Promise((resolve, reject) => {
DB.query("SELECT * FROM _backup WHERE mode=TRUE AND timestamp > ?", [timestamp]).then(result => {
let sync_needed = {};
result.forEach(r => r.t_name in sync_needed ? sync_needed[r.t_name].push(r.id) : sync_needed[r.t_name] = [r.id]);
ws.send(JSON.stringify({
command: "SYNC_HEADER",
add_data: result
}));
let promises = [];
for (let table in sync_needed)
promises.push(sendTable(table, sync_needed[table]));
Promise.allSettled(promises).then(result => {
let failedTables = [];
result.forEach(r => r.status === "rejected" ? failedTables.push(r.reason) : null);
if (failedTables.length)
reject(["dataSync", failedTables]);
else
resolve("dataSync");
});
}).catch(error => {
console.error(error);
reject("dataSync");
});
});
}
function backupSync_checksum(ws) {
return new Promise((resolve, reject) => {
DB.query("SELECT DISTINCT t_name FROM _backup").then(result => {
let tableList = result.map(r => r['t_name']);
DB.query("CHECKSUM TABLE " + tableList.join()).then(result => {
let checksum = Object.fromEntries(result.map(r => [r.Table.split(".").pop(), r.Checksum]));
ws.send(JSON.stringify({
command: "SYNC_CHECKSUM",
checksum: checksum
}));
resolve("checksum");
}).catch(error => {
console.error(error);
reject("checksum");
})
}).catch(error => {
console.error(error);
reject("checksum");
})
})
}
function sendTableHash(tables, ws) {
const getHash = table => new Promise((res, rej) => {
DB.query("SHOW COLUMNS FROM " + table).then(result => {
let columns = result.map(r => r["Field"]).sort();
DB.query(`SELECT CEIL(id/${HASH_N_ROW}) as group_id, MD5(GROUP_CONCAT(${columns.map(c => `IFNULL(${c}, "NULL")`).join()})) as hash FROM ${table} GROUP BY group_id ORDER BY group_id`)
.then(result => res(Object.fromEntries(result.map(r => [r.group_id, r.hash]))))
.catch(error => rej(error))
}).catch(error => rej(error))
});
Promise.allSettled(tables.map(t => getHash(t))).then(result => {
let hashes = {};
for (let i in tables)
if (result[i].status === "fulfilled")
hashes[tables[i]] = result[i].value;
else
console.error(result[i].reason);
ws.send(JSON.stringify({
command: "SYNC_HASH",
hashes: hashes
}));
})
}
function sendTableData(tables, ws) {
let promises = [
tableSync_data(tables, ws),
tableSync_delete(tables, ws),
tableSync_checksum(Object.keys(tables), ws)
];
Promise.allSettled(promises).then(result => {
let failedSync = [];
result.forEach(r => r.status === "rejected" ? failedSync.push(r.reason) : null);
if (failedSync.length) {
console.info("Backup Sync Failed:", failedSync);
ws.send(JSON.stringify({
command: "SYNC_END",
status: false,
info: failedSync
}));
} else {
console.info("Backup Sync completed");
ws.send(JSON.stringify({
command: "SYNC_END",
status: true
}));
}
});
}
function tableSync_delete(tables, ws) {
let getDelete = (table, group_id) => new Promise((res, rej) => {
let id_end = group_id * HASH_N_ROW,
id_start = id_end - HASH_N_ROW + 1;
DB.query("SELECT * FROM _backup WHERE t_name=? AND mode is NULL AND (id BETWEEN ? AND ?)", [table, id_start, id_end])
.then(result => res(result))
.catch(error => rej(error))
})
return new Promise((resolve, reject) => {
let promises = [];
for (let t in tables)
for (let g_id in tables[t][1]) //tables[t] is [convertIntArray(hash_ref), convertIntArray(hash_cur)]
promises.push(getDelete(t, g_id));
Promise.allSettled(promises).then(results => {
let delete_sync = results.filter(r => r.status === "fulfilled").map(r => r.value); //Filtered results
delete_sync = [].concat(...delete_sync); //Convert 2d array into 1d
ws.send(JSON.stringify({
command: "SYNC_DELETE",
delete_data: delete_sync
}));
resolve("deleteSync");
});
})
}
function tableSync_data(tables, ws) {
const sendTable = (table, group_id) => new Promise((res, rej) => {
let id_end = group_id * HASH_N_ROW,
id_start = id_end - HASH_N_ROW + 1;
DB.query(`SELECT * FROM ${table} WHERE id BETWEEN ? AND ?`, [id_start, id_end]).then(data => {
ws.send(JSON.stringify({
table,
command: "SYNC_UPDATE",
data
}));
res(table);
}).catch(error => {
console.error(error);
rej(table);
});
});
const getUpdate = (table, group_id) => new Promise((res, rej) => {
let id_end = group_id * HASH_N_ROW,
id_start = id_end - HASH_N_ROW + 1;
DB.query("SELECT * FROM _backup WHERE t_name=? AND mode=TRUE AND (id BETWEEN ? AND ?)", [table, id_start, id_end])
.then(result => res(result))
.catch(error => rej(error))
})
return new Promise((resolve, reject) => {
let promises = [];
for (let t in tables)
for (let g_id of tables[t][0]) //tables[t] is [convertIntArray(hash_ref), convertIntArray(hash_cur)]
promises.push(getUpdate(t, g_id));
Promise.allSettled(promises).then(results => {
let update_sync = results.filter(r => r.status === "fulfilled").map(r => r.value); //Filtered results
update_sync = [].concat(...update_sync); //Convert 2d array into 1d
ws.send(JSON.stringify({
command: "SYNC_HEADER",
add_data: update_sync
}));
let promises = [];
for (let t in tables)
for (let g_id of tables[t][0]) //tables[t] is [convertIntArray(hash_ref), convertIntArray(hash_cur)]
promises.push(sendTable(t, g_id));
Promise.allSettled(promises).then(result => {
let failedTables = [];
result.forEach(r => r.status === "rejected" ? failedTables.push(r.reason) : null);
if (failedTables.length)
reject(["dataSync", [...new Set(failedTables)]]);
else
resolve("dataSync");
});
});
})
}
function tableSync_checksum(tables, ws) {
return new Promise((resolve, reject) => {
DB.query("CHECKSUM TABLE " + tables.join()).then(result => {
let checksum = Object.fromEntries(result.map(r => [r.Table.split(".").pop(), r.Checksum]));
ws.send(JSON.stringify({
command: "SYNC_CHECKSUM",
checksum: checksum
}));
resolve("checksum");
}).catch(error => {
console.error(error);
reject("checksum");
})
})
}
module.exports = {
sendBackupData,
sendTableHash,
sendTableData,
set DB(db) {
DB = db;
}
}