-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add AutoBackupService; create backups for all projects on startup if …
…necessary
- Loading branch information
Showing
3 changed files
with
196 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,34 @@ | ||
import { Injectable } from '@angular/core'; | ||
import { SettingsProvider } from '../settings/settings-provider'; | ||
|
||
const remote = window.require('@electron/remote'); | ||
|
||
|
||
@Injectable() | ||
/** | ||
* @author Thomas Kleinke | ||
*/ | ||
export class AutoBackupService { | ||
|
||
private worker: Worker; | ||
|
||
constructor(private settingsProvider: SettingsProvider) {} | ||
|
||
|
||
public start() { | ||
|
||
const projectName: string = this.settingsProvider.getSettings().selectedProject; | ||
const backupsInfoFilePath: string = remote.getGlobal('appDataPath') + '/backups.json'; | ||
const backupDirectoryPath: string = this.settingsProvider.getSettings().backupDirectoryPath; | ||
const worker = new Worker(new URL('./create-backup.worker', import.meta.url)); | ||
worker.onmessage = ({ data }) => console.log(data); | ||
worker.postMessage({ projectName, backupDirectoryPath }); | ||
} | ||
this.worker = new Worker(new URL('./auto-backup.worker', import.meta.url)); | ||
|
||
this.worker.onmessage = ({ data }) => console.log(data); | ||
this.worker.postMessage({ | ||
command: 'start', | ||
settings: { | ||
backupsInfoFilePath, | ||
backupDirectoryPath, | ||
projects: this.settingsProvider.getSettings().dbs | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
/// <reference lib="webworker" /> | ||
|
||
const fs = require('fs'); | ||
const PouchDb = require('pouchdb-browser').default; | ||
|
||
|
||
type BackupsInfo = { | ||
backups: { [project: string]: Array<Backup> }; | ||
}; | ||
|
||
|
||
type Backup = { | ||
fileName: string; | ||
updateSequence: number; | ||
creationDate: Date; | ||
}; | ||
|
||
|
||
const projectQueue: string[] = []; | ||
|
||
let backupsInfoFilePath: string; | ||
let backupDirectoryPath: string; | ||
let projects: string[]; | ||
|
||
|
||
addEventListener('message', async ({ data }) => { | ||
|
||
const command: string = data.command; | ||
if (data.settings?.backupsInfoFilePath) backupsInfoFilePath = data.settings.backupsInfoFilePath; | ||
if (data.settings?.backupDirectoryPath) backupDirectoryPath = data.settings.backupDirectoryPath; | ||
if (data.settings?.projects) projects = data.settings.projects; | ||
|
||
switch (command) { | ||
case 'start': | ||
start(); | ||
break; | ||
} | ||
}); | ||
|
||
|
||
async function start() { | ||
|
||
initialize(); | ||
await update(); | ||
await runNextInQueue(); | ||
} | ||
|
||
|
||
function initialize() { | ||
|
||
if (!fs.existsSync(backupDirectoryPath)) fs.mkdirSync(backupDirectoryPath); | ||
} | ||
|
||
|
||
async function update() { | ||
|
||
const backupsInfo: BackupsInfo = loadBackupsInfo(); | ||
|
||
for (let project of projects) { | ||
if (await needsBackup(project, backupsInfo)) { | ||
projectQueue.push(project); | ||
} | ||
} | ||
} | ||
|
||
|
||
async function runNextInQueue() { | ||
|
||
if (!projectQueue.length) return; | ||
|
||
const project: string = projectQueue.shift(); | ||
await createBackup(project); | ||
|
||
runNextInQueue(); | ||
} | ||
|
||
|
||
function loadBackupsInfo(): BackupsInfo { | ||
|
||
const backupsInfo: BackupsInfo = deserializeBackupsInfo(); | ||
cleanUpBackupsInfo(backupsInfo); | ||
|
||
return backupsInfo; | ||
} | ||
|
||
|
||
async function needsBackup(project: string, backupsInfo: BackupsInfo): Promise<boolean> { | ||
|
||
const updateSequence = await getUpdateSequence(project); | ||
if (!updateSequence) return false; | ||
|
||
const backups: Array<Backup> = backupsInfo.backups[project] ?? []; | ||
if (!backups.length) return true; | ||
|
||
return backups.find(backup => { | ||
return updateSequence === backup.updateSequence; | ||
}) === undefined; | ||
} | ||
|
||
|
||
function cleanUpBackupsInfo(backupsInfo: BackupsInfo) { | ||
|
||
Object.entries(backupsInfo.backups).forEach(([project, backups]) => { | ||
backupsInfo.backups[project] = backups.filter(backup => fs.existsSync(backupDirectoryPath + '/' + backup.fileName)); | ||
}); | ||
|
||
serializeBackupsInfo(backupsInfo); | ||
} | ||
|
||
|
||
async function createBackup(project: string) { | ||
|
||
const updateSequence: number = await getUpdateSequence(project); | ||
const creationDate: Date = new Date(); | ||
const backupFileName: string = project + '_' + creationDate.toISOString().replace(/:/g, '-') + '.jsonl'; | ||
const backupFilePath: string = backupDirectoryPath + '/' + backupFileName; | ||
|
||
await createBackupInWorker(project, backupFilePath); | ||
addToBackupsInfo(project, backupFileName, updateSequence, creationDate); | ||
} | ||
|
||
|
||
async function createBackupInWorker(project: string, targetFilePath: string) { | ||
|
||
return new Promise<void>((resolve, reject) => { | ||
const worker = new Worker((new URL('./create-backup.worker', import.meta.url))); | ||
worker.onmessage = ({ data }) => { | ||
if (data.success) { | ||
resolve(); | ||
} else { | ||
reject(data.error); | ||
} | ||
} | ||
worker.postMessage({ project, targetFilePath }); | ||
}); | ||
} | ||
|
||
|
||
function addToBackupsInfo(project: string, fileName: string, updateSequence: number, creationDate: Date) { | ||
|
||
const backupsInfo: BackupsInfo = deserializeBackupsInfo(); | ||
|
||
if (!backupsInfo.backups[project]) backupsInfo.backups[project] = []; | ||
backupsInfo.backups[project].push({ | ||
fileName, | ||
updateSequence, | ||
creationDate | ||
}); | ||
|
||
serializeBackupsInfo(backupsInfo); | ||
} | ||
|
||
|
||
function deserializeBackupsInfo(): BackupsInfo { | ||
|
||
if (!fs.existsSync(backupsInfoFilePath)) return { backups: {} }; | ||
|
||
return JSON.parse(fs.readFileSync(backupsInfoFilePath, 'utf-8')); | ||
} | ||
|
||
|
||
function serializeBackupsInfo(backupsInfo: BackupsInfo) { | ||
|
||
fs.writeFileSync(backupsInfoFilePath, JSON.stringify(backupsInfo, null, 2)); | ||
} | ||
|
||
|
||
async function getUpdateSequence(project: string): Promise<number|undefined> { | ||
|
||
return (await new PouchDb(project).info()).update_seq; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters