From ec8194eff22d6aab44e507028e872b5a9e905027 Mon Sep 17 00:00:00 2001 From: tkleinke Date: Wed, 5 Mar 2025 17:46:27 +0100 Subject: [PATCH] Allow setting custom interval for keeping backups --- desktop/electron/main.js | 4 +- .../src/app/components/settings/settings.html | 14 + .../auto-backup/get-backups-to-delete.ts | 27 +- .../settings/keep-backups-settings.ts | 2 + .../image-sync-service.spec.ts | 2 + .../auto-backup/get-backups-to-delete.spec.ts | 248 ++++++++++++++++++ .../test/unit/subsystem/subsystem-helper.ts | 2 +- 7 files changed, 294 insertions(+), 5 deletions(-) diff --git a/desktop/electron/main.js b/desktop/electron/main.js index 2fdb998d50..e2b9277c07 100644 --- a/desktop/electron/main.js +++ b/desktop/electron/main.js @@ -49,7 +49,9 @@ global.setConfigDefaults = config => { if (config.highlightCustomElements === undefined) config.highlightCustomElements = true; setLanguages(config); if (os.type() === 'Linux') config.isAutoUpdateActive = false; - if (!config.keepBackups) config.keepBackups = { daily: 0, weekly: 0, monthly: 0 }; + if (!config.keepBackups) { + config.keepBackups = { custom: 0, customInterval: 0, daily: 0, weekly: 0, monthly: 0 }; + } return config; }; diff --git a/desktop/src/app/components/settings/settings.html b/desktop/src/app/components/settings/settings.html index 7c2d753ace..027816e888 100644 --- a/desktop/src/app/components/settings/settings.html +++ b/desktop/src/app/components/settings/settings.html @@ -171,6 +171,20 @@
Automatische Backups
+
+ + +
+
+ + +
, recentlyCreatedBackup function getOutdatedBackups(backups: Array, settings: KeepBackupsSettings): Array { + const custom: Array = getBackupsToKeepInCustomInterval(backups, settings.custom, settings.customInterval); const daily: Array = getBackupsToKeep(backups, settings.daily, isSameDay); const weekly: Array = getBackupsToKeep(backups, settings.weekly, isSameWeek); const monthly: Array = getBackupsToKeep(backups, settings.monthly, isSameMonth); return backups.filter(backup => { - return !daily.includes(backup) + return !custom.includes(backup) + && !daily.includes(backup) && !weekly.includes(backup) && !monthly.includes(backup) && backup !== backups[backups.length - 1]; @@ -55,8 +57,21 @@ function getOutdatedBackups(backups: Array, settings: KeepBackupsSetting } +function getBackupsToKeepInCustomInterval(backups: Array, amountToKeep: number, + intervalInHours: number): Array { + + return backups.slice().reduce((result, backup) => { + if (!result.length + || !isInCustomInterval(result[result.length - 1].creationDate, backup.creationDate, intervalInHours)) { + result.push(backup); + } + return result; + }, []).reverse().slice(0, amountToKeep); +} + + function getBackupsToKeep(backups: Array, amountToKeep: number, - isInSameTimespan: (date1: Date, date2: Date) => boolean) { + isInSameTimespan: (date1: Date, date2: Date) => boolean): Array { if (amountToKeep === 0) return []; @@ -86,3 +101,9 @@ function isSameWeek(date1: Date, date2: Date) { return sameWeek(date1, date2, { weekStartsOn: 1 }); } + + +function isInCustomInterval(date1: Date, date2: Date, intervalInHours: number): boolean { + + return differenceInHours(date2, date1, { roundingMethod: 'floor' }) < intervalInHours; +} diff --git a/desktop/src/app/services/settings/keep-backups-settings.ts b/desktop/src/app/services/settings/keep-backups-settings.ts index e746a5eb53..8a69ef519d 100644 --- a/desktop/src/app/services/settings/keep-backups-settings.ts +++ b/desktop/src/app/services/settings/keep-backups-settings.ts @@ -1,5 +1,7 @@ export interface KeepBackupsSettings { + custom: number; + customInterval: number; daily: number; weekly: number; monthly: number; diff --git a/desktop/test/hub-integration/image-sync-service.spec.ts b/desktop/test/hub-integration/image-sync-service.spec.ts index c166fa640e..adea347c66 100644 --- a/desktop/test/hub-integration/image-sync-service.spec.ts +++ b/desktop/test/hub-integration/image-sync-service.spec.ts @@ -61,6 +61,8 @@ describe('ImageSyncService', () => { backupDirectoryPath, isAutoUpdateActive: true, keepBackups: { + custom: 0, + customInterval: 0, daily: 0, weekly: 0, monthly: 0 diff --git a/desktop/test/unit/services/backup/auto-backup/get-backups-to-delete.spec.ts b/desktop/test/unit/services/backup/auto-backup/get-backups-to-delete.spec.ts index 2fe8b270c6..2806d56e28 100644 --- a/desktop/test/unit/services/backup/auto-backup/get-backups-to-delete.spec.ts +++ b/desktop/test/unit/services/backup/auto-backup/get-backups-to-delete.spec.ts @@ -38,6 +38,8 @@ describe('get backups to delete', () => { }; const settings: KeepBackupsSettings = { + custom: 0, + customInterval: 0, daily: 0, weekly: 0, monthly: 0 @@ -88,6 +90,8 @@ describe('get backups to delete', () => { }; const settings: KeepBackupsSettings = { + custom: 0, + customInterval: 0, daily: 1, weekly: 0, monthly: 0 @@ -148,6 +152,8 @@ describe('get backups to delete', () => { }; const settings: KeepBackupsSettings = { + custom: 0, + customInterval: 0, daily: 3, weekly: 0, monthly: 0 @@ -204,6 +210,8 @@ describe('get backups to delete', () => { }; const settings: KeepBackupsSettings = { + custom: 0, + customInterval: 0, daily: 1, weekly: 1, monthly: 0 @@ -265,6 +273,8 @@ describe('get backups to delete', () => { }; const settings: KeepBackupsSettings = { + custom: 0, + customInterval: 0, daily: 1, weekly: 1, monthly: 1 @@ -361,6 +371,8 @@ describe('get backups to delete', () => { }; const settings: KeepBackupsSettings = { + custom: 0, + customInterval: 0, daily: 3, weekly: 3, monthly: 3 @@ -385,6 +397,240 @@ describe('get backups to delete', () => { }); + test('custom interval: keep three backups, one backup every two hours', () => { + + const backups: BackupsMap = { + 'project': [ + { + filePath: 'file1.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T05:00:00+01:00') + }, + { + filePath: 'file2.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T06:00:00+01:00') + }, + { + filePath: 'file3.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T10:00:00+01:00') + }, + { + filePath: 'file4.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T12:50:00+01:00') + }, + { + filePath: 'file5.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T13:30:00+01:00') + }, + { + filePath: 'file6.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T14:00:00+01:00') + }, + { + filePath: 'file7.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T15:00:00+01:00') + } + ] + }; + + const settings: KeepBackupsSettings = { + custom: 3, + customInterval: 2, + daily: 0, + weekly: 0, + monthly: 0 + }; + + const recentlyCreatedBackups: { [project: string]: Array } = { + project: [ + { + filePath: 'file7.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T15:00:00+01:00') + } + ] + }; + + const result: Array = getBackupsToDelete(backups, recentlyCreatedBackups, settings); + console.log(result); + expect(result.length).toBe(4); + expect(result[0].filePath).toBe('file1.jsonl'); + expect(result[1].filePath).toBe('file2.jsonl'); + expect(result[2].filePath).toBe('file5.jsonl'); + expect(result[3].filePath).toBe('file6.jsonl'); + }); + + + test('custom interval: keep five backups, one backup per hour', () => { + + const backups: BackupsMap = { + 'project': [ + { + filePath: 'file1.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T05:00:00+01:00') + }, + { + filePath: 'file2.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T06:00:00+01:00') + }, + { + filePath: 'file3.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T07:00:00+01:00') + }, + { + filePath: 'file4.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T08:00:00+01:00') + }, + { + filePath: 'file5.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T09:00:00+01:00') + }, + { + filePath: 'file6.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T09:30:00+01:00') + }, + { + filePath: 'file7.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T10:00:00+01:00') + }, + { + filePath: 'file8.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T10:30:00+01:00') + }, + { + filePath: 'file9.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T11:00:00+01:00') + } + ] + }; + + const settings: KeepBackupsSettings = { + custom: 5, + customInterval: 1, + daily: 0, + weekly: 0, + monthly: 0 + }; + + const recentlyCreatedBackups: { [project: string]: Array } = { + project: [ + { + filePath: 'file9.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T11:00:00+01:00') + } + ] + }; + + const result: Array = getBackupsToDelete(backups, recentlyCreatedBackups, settings); + expect(result.length).toBe(4); + expect(result[0].filePath).toBe('file1.jsonl'); + expect(result[1].filePath).toBe('file2.jsonl'); + expect(result[2].filePath).toBe('file6.jsonl'); + expect(result[3].filePath).toBe('file8.jsonl'); + }); + + + test('combine custom interval with daily, weekly and monthly backups', () => { + + const backups: BackupsMap = { + 'project': [ + { + filePath: 'file1.jsonl', + project: 'project', + creationDate: new Date('2024-12-15T10:00:00+01:00') + }, + { + filePath: 'file2.jsonl', + project: 'project', + creationDate: new Date('2025-01-01T10:00:00+01:00') + }, + { + filePath: 'file3.jsonl', + project: 'project', + creationDate: new Date('2025-01-09T10:00:00+01:00') + }, + { + filePath: 'file4.jsonl', + project: 'project', + creationDate: new Date('2025-01-17T10:00:00+01:00') + }, + { + filePath: 'file5.jsonl', + project: 'project', + creationDate: new Date('2025-01-18T10:00:00+01:00') + }, + { + filePath: 'file6.jsonl', + project: 'project', + creationDate: new Date('2025-01-19T10:00:00+01:00') + }, + { + filePath: 'file7.jsonl', + project: 'project', + creationDate: new Date('2025-01-19T14:00:00+01:00') + }, + { + filePath: 'file8.jsonl', + project: 'project', + creationDate: new Date('2025-01-19T17:00:00+01:00') + }, + { + filePath: 'file9.jsonl', + project: 'project', + creationDate: new Date('2025-01-19T19:00:00+01:00') + }, + { + filePath: 'file10.jsonl', + project: 'project', + creationDate: new Date('2025-01-19T20:00:00+01:00') + } + ] + }; + + const settings: KeepBackupsSettings = { + custom: 2, + customInterval: 3, + daily: 1, + weekly: 1, + monthly: 1 + }; + + const recentlyCreatedBackups: { [project: string]: Array } = { + project: [ + { + filePath: 'file10.jsonl', + project: 'project', + creationDate: new Date('2025-01-19T20:00:00+01:00') + } + ] + }; + + const result: Array = getBackupsToDelete(backups, recentlyCreatedBackups, settings); + expect(result.length).toBe(5); + expect(result[0].filePath).toBe('file1.jsonl'); + expect(result[1].filePath).toBe('file3.jsonl'); + expect(result[2].filePath).toBe('file5.jsonl'); + expect(result[3].filePath).toBe('file7.jsonl'); + expect(result[4].filePath).toBe('file9.jsonl'); + }); + + test('delete recently updated backup', () => { const backups: BackupsMap = { @@ -403,6 +649,8 @@ describe('get backups to delete', () => { }; const settings: KeepBackupsSettings = { + custom: 0, + customInterval: 0, daily: 0, weekly: 0, monthly: 0 diff --git a/desktop/test/unit/subsystem/subsystem-helper.ts b/desktop/test/unit/subsystem/subsystem-helper.ts index 439bee83fb..f301d1ba6a 100644 --- a/desktop/test/unit/subsystem/subsystem-helper.ts +++ b/desktop/test/unit/subsystem/subsystem-helper.ts @@ -77,7 +77,7 @@ export async function setupSettingsService(pouchdbDatastore, projectIdentifier = imagestorePath: process.cwd() + '/test/test-temp/imagestore', backupDirectoryPath: process.cwd() + '/test/test-temp/backups', username: 'synctestuser', - keepBackups: { daily: 0, weekly: 0, monthly: 0 } + keepBackups: { custom: 0, customInterval: 0, daily: 0, weekly: 0, monthly: 0 } }); await settingsService.bootProjectDb(