From f53c77b9d8855c7e78242d662bf7fdd302a76ce6 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 1 Jul 2024 00:21:43 +0300 Subject: [PATCH] Prevent duplicate settings auto-backups --- src/endpoints/settings.js | 63 +++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/src/endpoints/settings.js b/src/endpoints/settings.js index 367ab6553..24986b6b0 100644 --- a/src/endpoints/settings.js +++ b/src/endpoints/settings.js @@ -3,7 +3,7 @@ const path = require('path'); const express = require('express'); const _ = require('lodash'); const writeFileAtomicSync = require('write-file-atomic').sync; -const { PUBLIC_DIRECTORIES, SETTINGS_FILE } = require('../constants'); +const { SETTINGS_FILE } = require('../constants'); const { getConfigValue, generateTimestamp, removeOldBackups } = require('../util'); const { jsonParser } = require('../express-common'); const { getAllUserHandles, getUserDirectories } = require('../users'); @@ -27,12 +27,12 @@ const AUTOSAVE_FUNCTIONS = new Map(); */ function triggerAutoSave(handle) { if (!AUTOSAVE_FUNCTIONS.has(handle)) { - const throttledAutoSave = _.throttle(() => backupUserSettings(handle), AUTOSAVE_INTERVAL); + const throttledAutoSave = _.throttle(() => backupUserSettings(handle, true), AUTOSAVE_INTERVAL); AUTOSAVE_FUNCTIONS.set(handle, throttledAutoSave); } const functionToCall = AUTOSAVE_FUNCTIONS.get(handle); - if (functionToCall) { + if (functionToCall && typeof functionToCall === 'function') { functionToCall(); } } @@ -113,7 +113,7 @@ async function backupSettings() { const userHandles = await getAllUserHandles(); for (const handle of userHandles) { - backupUserSettings(handle); + backupUserSettings(handle, true); } } catch (err) { console.log('Could not backup settings file', err); @@ -123,13 +123,18 @@ async function backupSettings() { /** * Makes a backup of the user's settings file. * @param {string} handle User handle + * @param {boolean} preventDuplicates Prevent duplicate backups * @returns {void} */ -function backupUserSettings(handle) { +function backupUserSettings(handle, preventDuplicates) { const userDirectories = getUserDirectories(handle); const backupFile = path.join(userDirectories.backups, `${getFilePrefix(handle)}${generateTimestamp()}.json`); const sourceFile = path.join(userDirectories.root, SETTINGS_FILE); + if (preventDuplicates && isDuplicateBackup(handle, sourceFile)) { + return; + } + if (!fs.existsSync(sourceFile)) { return; } @@ -138,6 +143,52 @@ function backupUserSettings(handle) { removeOldBackups(userDirectories.backups, `settings_${handle}`); } +/** + * Checks if the backup would be a duplicate. + * @param {string} handle User handle + * @param {string} sourceFile Source file path + * @returns {boolean} True if the backup is a duplicate + */ +function isDuplicateBackup(handle, sourceFile) { + const latestBackup = getLatestBackup(handle); + if (!latestBackup) { + return false; + } + return areFilesEqual(latestBackup, sourceFile); +} + +/** + * Returns true if the two files are equal. + * @param {string} file1 File path + * @param {string} file2 File path + */ +function areFilesEqual(file1, file2) { + if (!fs.existsSync(file1) || !fs.existsSync(file2)) { + return false; + } + + const content1 = fs.readFileSync(file1); + const content2 = fs.readFileSync(file2); + return content1.toString() === content2.toString(); +} + +/** + * Gets the latest backup file for a user. + * @param {string} handle User handle + * @returns {string|null} Latest backup file. Null if no backup exists. + */ +function getLatestBackup(handle) { + const userDirectories = getUserDirectories(handle); + const backupFiles = fs.readdirSync(userDirectories.backups) + .filter(x => x.startsWith(getFilePrefix(handle))) + .map(x => ({ name: x, ctime: fs.statSync(path.join(userDirectories.backups, x)).ctimeMs })); + const latestBackup = backupFiles.sort((a, b) => b.ctime - a.ctime)[0]?.name; + if (!latestBackup) { + return null; + } + return path.join(userDirectories.backups, latestBackup); +} + const router = express.Router(); router.post('/save', jsonParser, function (request, response) { @@ -265,7 +316,7 @@ router.post('/load-snapshot', jsonParser, async (request, response) => { router.post('/make-snapshot', jsonParser, async (request, response) => { try { - backupUserSettings(request.user.profile.handle); + backupUserSettings(request.user.profile.handle, false); response.sendStatus(204); } catch (error) { console.log(error);