Skip to content

Commit

Permalink
feat: Added force backup
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkoelle committed Jun 30, 2021
1 parent 95d7993 commit 5d44e33
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 46 deletions.
64 changes: 53 additions & 11 deletions app/backup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'fs';
import * as Path from 'path';
import { app, IpcMainEvent, ipcMain } from 'electron';
import { app, ipcMain, WebContents } from 'electron';
import * as BackupIPC from './constants/BackupIPC';

export default class Backup {
Expand All @@ -23,44 +23,57 @@ export default class Backup {
fs.mkdirSync(Backup.backupDir);
}

ipcMain.on(BackupIPC.BACKUP_ONCE, (event, p) => {
this.makeBackupOnce(event.sender, p);
});

ipcMain.on(BackupIPC.BACKUP_START, (event, p) => {
this.startBackup(event, p);
this.startBackup(event.sender, p);
});

ipcMain.on(BackupIPC.BACKUP_STOP, () => {
this.stopBackup();
});
}

public startBackup(event: IpcMainEvent, filePath: string) {
public startBackup(
webContents: WebContents,
filePath: string,
iteration = 0
) {
this.path = filePath;
this.iteration = 0;
this.webContents = event.sender;
this.iteration = iteration;
this.webContents = webContents;
this.intervalId = setInterval(() => this.makeBackup(), this.interval);
}

public stopBackup() {
this.webContents = undefined;
this.path = undefined;
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = undefined;
}
}

public makeBackup() {
this.deleteOldBackups();

if (this.path) {
const dest = Path.join(
Backup.backupDir,
`${Path.basename(this.path)}.bak${this.iteration + 1}`
);
const fileName =
this.iteration >= 0
? `${Path.basename(this.path)}.bak${this.iteration + 1}`
: `${Path.basename(this.path)}.bak${new Date().toISOString()}`;
const dest = Path.join(Backup.backupDir, fileName);

fs.copyFileSync(this.path, dest);

if (this.webContents !== undefined) {
// TODO: check if backup was really successful
if (fs.existsSync(dest)) {
this.iteration = (this.iteration + 1) % 3;
if (this.iteration >= 0) {
this.iteration = (this.iteration + 1) % 3;
}
this.webContents.send(BackupIPC.BACKUP_SUCCESSFUL);
} else {
this.webContents.send(BackupIPC.BACKUP_FAILED);
Expand All @@ -77,7 +90,7 @@ export default class Backup {
const path = Path.join(Backup.backupDir, p);
if (fs.existsSync(path)) {
const stats = fs.statSync(path);
const date = new Date(stats.atimeMs);
const date = new Date(stats.mtimeMs);
const now = new Date();
const diff = now.getTime() - date.getTime();
if (diff >= this.retention) {
Expand All @@ -86,4 +99,33 @@ export default class Backup {
}
});
}

public makeBackupOnce(webContents: WebContents, path: string) {
// Temporary store current values
const pathTemp = this.path;
const wcTemp = this.webContents;
const itTemp = this.iteration;
const intTemp = this.intervalId !== undefined;

// Clear active backup job
this.stopBackup();

// Initialize new values
this.path = path;
this.webContents = webContents;
this.iteration = -1;

// Make backup
this.makeBackup();

// Restore previously active backup
if (
intTemp &&
pathTemp !== undefined &&
wcTemp !== undefined &&
intTemp !== undefined
) {
this.startBackup(wcTemp, pathTemp, itTemp);
}
}
}
1 change: 1 addition & 0 deletions app/constants/BackupIPC.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const BACKUP_START = 'BACKUP_START';
export const BACKUP_STOP = 'BACKUP_STOP';
export const BACKUP_ONCE = 'BACKUP_ONCE';
export const BACKUP_SUCCESSFUL = 'BACKUP_SUCCESSFUL';
export const BACKUP_FAILED = 'BACKUP_FAILED';
88 changes: 64 additions & 24 deletions app/menu/FileMenu.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { OpenDialogReturnValue, remote, SaveDialogReturnValue } from 'electron';
import {
ipcRenderer,
OpenDialogReturnValue,
remote,
SaveDialogReturnValue,
} from 'electron';
import fs from 'fs';
import * as Path from 'path';
import * as BackupIPC from '../constants/BackupIPC';
import ConfirmationDialog from '../dialogs/ConfirmationDialog';
import UnsavedChangesDialog from '../dialogs/UnsavedChangesDialog';
import {
Expand Down Expand Up @@ -35,9 +41,19 @@ const buildFileMenu = (
dispatch(reloadState());
}
};
const backupPaths = fs
const backupPaths: { path: string; date: Date }[] = fs
.readdirSync(Path.join(remote.app.getPath('userData'), 'Backup'))
.filter((p) => workspace && p.includes(Path.basename(workspace)));
.filter((path) => workspace && path.includes(Path.basename(workspace)))
.map((path) => {
const fullPath = Path.join(
remote.app.getPath('userData'),
'Backup',
path
);
const stats = fs.statSync(fullPath);
const date = new Date(stats.mtimeMs);
return { path, date };
});

return {
label: 'File',
Expand Down Expand Up @@ -123,30 +139,54 @@ const buildFileMenu = (
},
},
{
label: 'Restore Backup',
disabled: backupPaths.length <= 0,
submenu: backupPaths.map((path) => {
return {
label: path,
label: 'Backups',
submenu: [
{
label: 'Force Backup',
click: async () => {
ipcRenderer.send(BackupIPC.BACKUP_ONCE, workspace);
},
},
{
label: 'Restore Backup',
disabled: backupPaths.length <= 0,
submenu: backupPaths.map((backup) => {
const now = new Date();
const diff = now.getTime() - backup.date.getTime();
const mins = Math.round(diff / 60000);
return {
label: `${backup.path} ${
mins === 0 ? '(now)' : `(${mins} min ago)`
}`,
click: async () => {
const fullPath = Path.join(
remote.app.getPath('userData'),
'Backup',
backup.path
);
if (fs.existsSync(fullPath)) {
showModal(ConfirmationDialog, {
title: 'Restore Backup?',
text: `Do you really want to restore backup "${backup.path}"?`,
onConfirm: () => {
fs.renameSync(fullPath, workspace);
dispatch(reloadState());
},
});
}
},
};
}),
},
{
label: 'Show Backups Folder',
click: async () => {
const fullPath = Path.join(
remote.app.getPath('userData'),
'Backup',
path
remote.shell.openPath(
Path.join(remote.app.getPath('userData'), 'Backup')
);
if (fs.existsSync(fullPath)) {
showModal(ConfirmationDialog, {
title: 'Restore Backup?',
text: `Do you really want to restore backup "${path}"?`,
onConfirm: () => {
fs.renameSync(fullPath, workspace);
dispatch(reloadState());
},
});
}
},
};
}),
},
],
},
{
label: 'Close file',
Expand Down
12 changes: 1 addition & 11 deletions app/menu/HelpMenu.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { MenuItemConstructorOptions, remote, shell } from 'electron';
import * as Path from 'path';
import { MenuItemConstructorOptions, shell } from 'electron';
import ReleaseNotesModal from '../modals/ReleaseNotesModal';
import UpdaterModal from '../modals/UpdaterModal';

Expand All @@ -20,15 +19,6 @@ const buildHelpMenu = (showModal): MenuItemConstructorOptions => {
},
},
{ type: 'separator' },
{
label: 'Backups Folder',
click: async () => {
remote.shell.openPath(
Path.join(remote.app.getPath('userData'), 'Backup')
);
},
},
{ type: 'separator' },
{
label: 'Documentation',
click() {
Expand Down

0 comments on commit 5d44e33

Please sign in to comment.