diff --git a/lib/backup/Backup.ts b/lib/backup/Backup.ts index 51c6b39f8..37ca67027 100644 --- a/lib/backup/Backup.ts +++ b/lib/backup/Backup.ts @@ -1,4 +1,3 @@ -import { createHash } from 'crypto'; import { EventEmitter } from 'events'; import fs from 'fs'; import path from 'path'; @@ -9,7 +8,9 @@ import { getDefaultBackupDir } from '../utils/utils'; interface Backup { on(event: 'newBackup', listener: (path: string) => void): this; + on(event: 'changeDetected', listener: (client: string) => void): this; emit(event: 'newBackup', path: string): boolean; + emit(event: 'changeDetected', client: string): boolean; } class Backup extends EventEmitter { @@ -22,6 +23,18 @@ class Backup extends EventEmitter { private lndClients: LndClient[] = []; private checkLndTimer: ReturnType | undefined; + /** A map of client names to a boolean indicating whether they have changed since the last backup. */ + private databaseChangedMap = new Map(); + + private xudBackupTimer = setInterval(() => { + const backupPath = this.getBackupPath('xud'); + if (this.databaseChangedMap.get('xud') === true) { + const content = this.readDatabase(this.config.dbpath); + this.writeBackup(backupPath, content); + this.databaseChangedMap.set('xud', false); + } + }, 180000); + public start = async (args: { [argName: string]: any }) => { await this.config.load(args); @@ -65,6 +78,8 @@ class Backup extends EventEmitter { for (const lndClient of this.lndClients) { lndClient.close(); } + + clearInterval(this.xudBackupTimer); } private waitForLndConnected = (lndClient: LndClient) => { @@ -131,15 +146,13 @@ class Backup extends EventEmitter { } private startFilewatcher = async (client: string, dbPath: string) => { - let previousDatabaseHash: string | undefined; const backupPath = this.getBackupPath(client); if (fs.existsSync(dbPath)) { this.logger.verbose(`Writing initial ${client} database backup to: ${backupPath}`); - const { content, hash } = this.readDatabase(dbPath); + const content = this.readDatabase(dbPath); this.writeBackup(backupPath, content); - previousDatabaseHash = hash; } else { this.logger.warn(`Could not find database file of ${client} at ${dbPath}, waiting for it to be created...`); const dbDir = path.dirname(dbPath); @@ -157,29 +170,19 @@ class Backup extends EventEmitter { this.fileWatchers.push(fs.watch(dbPath, { persistent: true, recursive: false }, (event: string) => { if (event === 'change') { - const { content, hash } = this.readDatabase(dbPath); - - // Compare the MD5 hash of the current content of the file with hash of the content when - // it was backed up the last time to ensure that the content of the file has changed - if (hash !== previousDatabaseHash) { - this.logger.trace(`${client} database changed`); - - previousDatabaseHash = hash; - this.writeBackup(backupPath, content); - } + this.logger.trace(`${client} database changed`); + this.emit('changeDetected', client); + this.databaseChangedMap.set(client, true); } })); this.logger.verbose(`Listening for changes to the ${client} database`); } - private readDatabase = (path: string): { content: Buffer, hash: string } => { + private readDatabase = (path: string) => { const content = fs.readFileSync(path); - return { - content, - hash: createHash('md5').update(content).digest('base64'), - }; + return content; } private writeBackup = (backupPath: string, data: Uint8Array) => { diff --git a/test/jest/Backup.spec.ts b/test/jest/Backup.spec.ts index 8e34d150b..e03ab4d53 100644 --- a/test/jest/Backup.spec.ts +++ b/test/jest/Backup.spec.ts @@ -14,7 +14,6 @@ const removeDir = (dir: string) => { const backupdir = 'backup-test'; -const raidenDatabasePath = 'raiden'; const xudDatabasePath = 'xud'; const backups = { @@ -22,10 +21,6 @@ const backups = { event: 'lnd event', startup: 'lnd startup', }, - raiden: { - event: 'raiden event', - startup: 'raiden startup', - }, xud: { event: 'xud event', startup: 'xud startup', @@ -35,7 +30,6 @@ const backups = { let channelBackupCallback: any; const onListenerMock = jest.fn((event, callback) => { - if (event === 'channelBackup') { channelBackupCallback = callback; } else { @@ -61,29 +55,20 @@ describe('Backup', () => { const backup = new Backup(); beforeAll(async () => { - await Promise.all([ - fs.promises.writeFile( - raidenDatabasePath, - backups.raiden.startup, - ), - fs.promises.writeFile( - xudDatabasePath, - backups.xud.startup, - ), - ]); + await fs.promises.writeFile( + xudDatabasePath, + backups.xud.startup, + ); await backup.start({ backupdir, loglevel: 'error', dbpath: xudDatabasePath, - raiden: { - dbpath: raidenDatabasePath, - }, }); }); - afterAll(async () => { - await backup.stop(); + afterAll(() => { + backup.stop(); }); test('should write LND backups on startup', () => { @@ -106,39 +91,6 @@ describe('Backup', () => { ).toEqual(backups.lnd.event); }); - test('should write Raiden backups on startup', () => { - expect( - fs.readFileSync( - path.join(backupdir, 'raiden'), - 'utf8', - ), - ).toEqual(backups.raiden.startup); - }); - - test('should write Raiden backups on new event', async () => { - fs.writeFileSync( - raidenDatabasePath, - backups.raiden.event, - ); - - // Wait to make sure the file watcher handled the new file - await new Promise((resolve, reject) => { - setTimeout(reject, 3000); - backup.on('newBackup', (path) => { - if (path.endsWith(raidenDatabasePath)) { - resolve(); - } - }); - }); - - expect( - fs.readFileSync( - path.join(backupdir, 'raiden'), - 'utf8', - ), - ).toEqual(backups.raiden.event); - }); - test('should write XUD database backups on startup', () => { expect( fs.readFileSync( @@ -148,7 +100,7 @@ describe('Backup', () => { ).toEqual(backups.xud.startup); }); - test('should write XUD database backups on new event', async () => { + test('should detect XUD database backups on new event', async () => { fs.writeFileSync( xudDatabasePath, backups.xud.event, @@ -157,29 +109,19 @@ describe('Backup', () => { // Wait to make sure the file watcher handled the new file await new Promise((resolve, reject) => { setTimeout(reject, 3000); - backup.on('newBackup', (path) => { + backup.on('changeDetected', (path) => { if (path.endsWith(xudDatabasePath)) { resolve(); } }); }); - - expect( - fs.readFileSync( - path.join(backupdir, 'xud'), - 'utf8', - ), - ).toEqual(backups.xud.event); }); afterAll(async () => { - await backup.stop(); + backup.stop(); removeDir(backupdir); - await Promise.all([ - fs.promises.unlink(xudDatabasePath), - fs.promises.unlink(raidenDatabasePath), - ]); + await fs.promises.unlink(xudDatabasePath); }); });