2020-01-07 17:50:56 +00:00
|
|
|
/*
|
|
|
|
* Offer functions for Canvas backups
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* eslint-disable no-console */
|
|
|
|
|
|
|
|
import sharp from 'sharp';
|
|
|
|
import fs from 'fs';
|
2022-04-06 19:50:34 +00:00
|
|
|
import { commandOptions } from 'redis';
|
2020-01-07 17:50:56 +00:00
|
|
|
|
2022-04-06 19:50:34 +00:00
|
|
|
import Palette from './Palette';
|
2020-01-07 17:50:56 +00:00
|
|
|
import { TILE_SIZE } from './constants';
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy canvases from one redis instance to another
|
|
|
|
* @param canvasRedis redis from where to get the data
|
|
|
|
* @param backupRedis redis where to write the data to
|
|
|
|
* @param canvases Object with all canvas informations
|
|
|
|
*/
|
|
|
|
export async function updateBackupRedis(canvasRedis, backupRedis, canvases) {
|
|
|
|
const ids = Object.keys(canvases);
|
|
|
|
for (let i = 0; i < ids.length; i += 1) {
|
|
|
|
const id = ids[i];
|
|
|
|
const canvas = canvases[id];
|
2020-07-05 09:22:40 +00:00
|
|
|
if (canvas.v || canvas.hid) {
|
|
|
|
// ignore 3D and hiddedn canvases
|
2020-01-22 14:34:46 +00:00
|
|
|
continue;
|
|
|
|
}
|
2020-01-07 17:50:56 +00:00
|
|
|
const chunksXY = (canvas.size / TILE_SIZE);
|
|
|
|
console.log('Copy Chunks to backup redis...');
|
|
|
|
const startTime = Date.now();
|
|
|
|
let amount = 0;
|
|
|
|
for (let x = 0; x < chunksXY; x++) {
|
|
|
|
for (let y = 0; y < chunksXY; y++) {
|
|
|
|
const key = `ch:${id}:${x}:${y}`;
|
|
|
|
/*
|
|
|
|
* await on every iteration is fine because less resource usage
|
|
|
|
* in exchange for higher execution time is wanted.
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
2022-04-06 19:50:34 +00:00
|
|
|
const chunk = await canvasRedis.get(
|
|
|
|
commandOptions({ returnBuffers: true }),
|
|
|
|
key,
|
|
|
|
);
|
2020-01-07 17:50:56 +00:00
|
|
|
if (chunk) {
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
2022-04-06 19:50:34 +00:00
|
|
|
await backupRedis.set(key, chunk);
|
2020-01-07 17:50:56 +00:00
|
|
|
amount += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const time = Date.now() - startTime;
|
|
|
|
console.log(`Finished Copying ${amount} chunks in ${time}ms.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create incremential PNG tile backup between two redis canvases
|
|
|
|
* @param canvasRedis redis from where to get the data
|
|
|
|
* @param backupRedis redis where to write the data to
|
|
|
|
* @param canvases Object with all canvas informations
|
|
|
|
*/
|
|
|
|
export async function incrementialBackupRedis(
|
|
|
|
canvasRedis,
|
|
|
|
backupRedis,
|
|
|
|
canvases,
|
2022-04-06 19:50:34 +00:00
|
|
|
backupDir,
|
2020-01-07 17:50:56 +00:00
|
|
|
) {
|
|
|
|
const ids = Object.keys(canvases);
|
|
|
|
for (let i = 0; i < ids.length; i += 1) {
|
|
|
|
const id = ids[i];
|
|
|
|
|
2020-01-22 14:34:46 +00:00
|
|
|
const canvas = canvases[id];
|
2020-07-05 09:22:40 +00:00
|
|
|
if (canvas.v || canvas.hid) {
|
|
|
|
// ignore 3D and hidden canvases
|
2020-01-22 14:34:46 +00:00
|
|
|
continue;
|
|
|
|
}
|
2020-01-07 17:50:56 +00:00
|
|
|
|
|
|
|
const canvasBackupDir = `${backupDir}/${id}`;
|
|
|
|
if (!fs.existsSync(canvasBackupDir)) {
|
|
|
|
fs.mkdirSync(canvasBackupDir);
|
|
|
|
}
|
2020-01-08 00:29:52 +00:00
|
|
|
const date = new Date();
|
2020-01-08 01:53:10 +00:00
|
|
|
let hours = date.getHours();
|
|
|
|
let minutes = date.getMinutes();
|
|
|
|
if (hours < 10) hours = `0${hours}`;
|
|
|
|
if (minutes < 10) minutes = `0${minutes}`;
|
|
|
|
const canvasTileBackupDir = `${canvasBackupDir}/${hours}${minutes}`;
|
2020-01-08 14:15:36 +00:00
|
|
|
console.log(`Using folder ${canvasTileBackupDir}`);
|
2020-01-07 17:50:56 +00:00
|
|
|
if (!fs.existsSync(canvasTileBackupDir)) {
|
|
|
|
fs.mkdirSync(canvasTileBackupDir);
|
|
|
|
}
|
|
|
|
|
2020-01-30 23:26:32 +00:00
|
|
|
const palette = new Palette(canvas.colors);
|
2020-01-07 17:50:56 +00:00
|
|
|
const chunksXY = (canvas.size / TILE_SIZE);
|
|
|
|
console.log('Creating Incremential Backup...');
|
|
|
|
const startTime = Date.now();
|
|
|
|
let amount = 0;
|
|
|
|
for (let x = 0; x < chunksXY; x++) {
|
|
|
|
const xBackupDir = `${canvasTileBackupDir}/${x}`;
|
|
|
|
let createdDir = false;
|
|
|
|
|
|
|
|
for (let y = 0; y < chunksXY; y++) {
|
|
|
|
const key = `ch:${id}:${x}:${y}`;
|
|
|
|
/*
|
|
|
|
* await on every iteration is fine because less resource usage
|
|
|
|
* in exchange for higher execution time is wanted.
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
2022-04-06 19:50:34 +00:00
|
|
|
const curChunk = await canvasRedis.get(
|
|
|
|
commandOptions({ returnBuffers: true }),
|
|
|
|
key,
|
|
|
|
);
|
2020-01-07 17:50:56 +00:00
|
|
|
let tileBuffer = null;
|
|
|
|
if (curChunk) {
|
2020-06-10 23:20:36 +00:00
|
|
|
if (curChunk.length === TILE_SIZE * TILE_SIZE) {
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
2022-04-06 19:50:34 +00:00
|
|
|
const oldChunk = await backupRedis.get(
|
|
|
|
commandOptions({ returnBuffers: true }),
|
|
|
|
key,
|
|
|
|
);
|
2020-06-10 23:20:36 +00:00
|
|
|
if (oldChunk && oldChunk.length === TILE_SIZE * TILE_SIZE) {
|
|
|
|
let pxl = 0;
|
|
|
|
while (pxl < curChunk.length) {
|
|
|
|
if (curChunk[pxl] !== oldChunk[pxl]) {
|
|
|
|
if (!tileBuffer) {
|
|
|
|
tileBuffer = new Uint32Array(TILE_SIZE * TILE_SIZE);
|
|
|
|
}
|
|
|
|
const color = palette.abgr[curChunk[pxl] & 0x3F];
|
|
|
|
tileBuffer[pxl] = color;
|
2020-01-07 17:50:56 +00:00
|
|
|
}
|
2020-06-10 23:20:36 +00:00
|
|
|
pxl += 1;
|
2020-01-07 17:50:56 +00:00
|
|
|
}
|
2020-06-10 23:20:36 +00:00
|
|
|
} else {
|
|
|
|
tileBuffer = palette.buffer2ABGR(curChunk);
|
2020-01-07 17:50:56 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-06-10 23:20:36 +00:00
|
|
|
console.log(
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
`Chunk ${x},${y} of canvas ${id} has invalid length ${curChunk.length}`,
|
|
|
|
);
|
2020-01-07 17:50:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (tileBuffer) {
|
|
|
|
if (!createdDir && !fs.existsSync(xBackupDir)) {
|
|
|
|
createdDir = true;
|
|
|
|
fs.mkdirSync(xBackupDir);
|
|
|
|
}
|
|
|
|
const filename = `${xBackupDir}/${y}.png`;
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
await sharp(
|
|
|
|
Buffer.from(tileBuffer.buffer), {
|
|
|
|
raw: {
|
|
|
|
width: TILE_SIZE,
|
|
|
|
height: TILE_SIZE,
|
|
|
|
channels: 4,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
).toFile(filename);
|
|
|
|
amount += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const time = Date.now() - startTime;
|
|
|
|
console.log(
|
|
|
|
`Finished Incremential backup of ${amount} chunks in ${time}ms.`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Backup all tiles as PNG files into folder
|
|
|
|
* @param redisClient RedisClient
|
|
|
|
* @param canvases Object with the informations to all canvases
|
|
|
|
* @param backupDir directory where to save png tiles
|
|
|
|
*/
|
|
|
|
export async function createPngBackup(
|
2022-04-06 19:50:34 +00:00
|
|
|
redisClient,
|
|
|
|
canvases,
|
|
|
|
backupDir,
|
2020-01-07 17:50:56 +00:00
|
|
|
) {
|
|
|
|
const ids = Object.keys(canvases);
|
|
|
|
for (let i = 0; i < ids.length; i += 1) {
|
|
|
|
const id = ids[i];
|
|
|
|
|
|
|
|
const canvasBackupDir = `${backupDir}/${id}`;
|
|
|
|
if (!fs.existsSync(canvasBackupDir)) {
|
|
|
|
fs.mkdirSync(canvasBackupDir);
|
|
|
|
}
|
|
|
|
const canvasTileBackupDir = `${canvasBackupDir}/tiles`;
|
|
|
|
if (!fs.existsSync(canvasTileBackupDir)) {
|
|
|
|
fs.mkdirSync(canvasTileBackupDir);
|
|
|
|
}
|
|
|
|
|
|
|
|
const canvas = canvases[id];
|
2020-01-30 23:26:32 +00:00
|
|
|
const palette = new Palette(canvas.colors);
|
2020-01-07 17:50:56 +00:00
|
|
|
const chunksXY = (canvas.size / TILE_SIZE);
|
|
|
|
console.log('Create PNG tiles from backup...');
|
|
|
|
const startTime = Date.now();
|
|
|
|
let amount = 0;
|
|
|
|
for (let x = 0; x < chunksXY; x++) {
|
|
|
|
const xBackupDir = `${canvasTileBackupDir}/${x}`;
|
|
|
|
if (!fs.existsSync(xBackupDir)) {
|
|
|
|
fs.mkdirSync(xBackupDir);
|
|
|
|
}
|
|
|
|
for (let y = 0; y < chunksXY; y++) {
|
|
|
|
const key = `ch:${id}:${x}:${y}`;
|
2020-05-29 02:46:27 +00:00
|
|
|
try {
|
|
|
|
/*
|
|
|
|
* await on every iteration is fine because less resource usage
|
|
|
|
* in exchange for higher execution time is wanted.
|
|
|
|
*/
|
2020-01-07 17:50:56 +00:00
|
|
|
// eslint-disable-next-line no-await-in-loop
|
2022-04-06 19:50:34 +00:00
|
|
|
const chunk = await redisClient.get(
|
|
|
|
commandOptions({ returnBuffers: true }),
|
|
|
|
key,
|
|
|
|
);
|
2020-05-29 02:46:27 +00:00
|
|
|
if (chunk) {
|
|
|
|
if (chunk.length === TILE_SIZE * TILE_SIZE) {
|
|
|
|
const textureBuffer = palette.buffer2RGB(chunk);
|
|
|
|
const filename = `${xBackupDir}/${y}.png`;
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
await sharp(
|
|
|
|
Buffer.from(textureBuffer.buffer), {
|
|
|
|
raw: {
|
|
|
|
width: TILE_SIZE,
|
|
|
|
height: TILE_SIZE,
|
|
|
|
channels: 3,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
).toFile(filename);
|
|
|
|
amount += 1;
|
|
|
|
} else {
|
|
|
|
console.log(
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
`Chunk ${x},${y} of canvas ${id} has invalid length ${chunk.length}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
console.log(
|
|
|
|
`Couldn't create PNG backup of chunk ${x},${y} of canvas ${id}.`,
|
|
|
|
);
|
2020-01-07 17:50:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const time = Date.now() - startTime;
|
|
|
|
console.log(
|
|
|
|
`Finished creating PNG backup of ${amount} chunks in ${time}ms.`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|