Skip to content

Commit 9cdb552

Browse files
author
Erika Perugachi
authored
Merge pull request #1100 from JulianAdams4/automatic-backup
Automatic backup
2 parents 3387dc7 + ca78473 commit 9cdb552

22 files changed

+1135
-324
lines changed

electron_app/electron-starter.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ require('./src/ipc/mailbox.js');
2424
require('./src/ipc/database.js');
2525
require('./src/ipc/manager.js');
2626
require('./src/ipc/dataTransfer.js');
27+
require('./src/ipc/backup.js');
2728
const ipcUtils = require('./src/ipc/utils.js');
2829

2930
globalManager.forcequit.set(false);

electron_app/src/BackupManager.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const copy = require('recursive-copy');
66
const myAccount = require('./Account');
77
const { databasePath } = require('./models');
88
const { APP_DOMAIN } = require('./utils/const');
9+
const { backupFilenameRegex } = require('./utils/RegexUtils');
910
const { createPathRecursive, getUserEmailsPath } = require('./utils/FileUtils');
1011
const {
1112
decryptStreamFileWithPassword,
@@ -71,6 +72,18 @@ const removeTempBackupDirectoryRecursive = pathToDelete => {
7172
}
7273
};
7374

75+
const cleanPreviousBackupFilesInFolder = pathToClean => {
76+
try {
77+
const files = fs.readdirSync(pathToClean);
78+
const filtered = files.filter(name => backupFilenameRegex.test(name));
79+
filtered.forEach(filename => {
80+
fs.unlinkSync(path.join(pathToClean, filename));
81+
});
82+
} catch (cleanErr) {
83+
return cleanErr;
84+
}
85+
};
86+
7487
/* Default Directory
7588
----------------------------- */
7689
function getUsername() {
@@ -155,6 +168,15 @@ const prepareBackupFiles = async ({ backupPrevFiles = true }) => {
155168
}
156169
};
157170

171+
const getFileSizeInBytes = filename => {
172+
try {
173+
const stats = fs.statSync(filename);
174+
return stats.size;
175+
} catch (error) {
176+
return 0;
177+
}
178+
};
179+
158180
/* Export Backup
159181
----------------------------- */
160182
const exportBackupUnencrypted = async ({ backupPath }) => {
@@ -178,10 +200,12 @@ const exportBackupUnencrypted = async ({ backupPath }) => {
178200
}
179201
// Move to destination
180202
try {
203+
cleanPreviousBackupFilesInFolder(path.join(backupPath, '..'));
181204
fs.writeFileSync(backupPath, fs.readFileSync(ExportZippedFilename));
182205
} catch (fileErr) {
183206
throw new Error('Failed to move backup file');
184207
}
208+
return getFileSizeInBytes(backupPath);
185209
} catch (exportBackupError) {
186210
throw exportBackupError;
187211
} finally {
@@ -215,10 +239,12 @@ const exportBackupEncrypted = async ({ backupPath, password }) => {
215239
}
216240
// Move to destination
217241
try {
242+
cleanPreviousBackupFilesInFolder(path.join(backupPath, '..'));
218243
fs.writeFileSync(backupPath, fs.readFileSync(ExportEncryptedFilename));
219244
} catch (fileErr) {
220245
throw new Error('Failed to create backup file');
221246
}
247+
return getFileSizeInBytes(backupPath);
222248
} catch (exportBackupError) {
223249
throw exportBackupError;
224250
} finally {

electron_app/src/DBManager.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,15 +1237,30 @@ const deletePendingEventsByIds = ids => {
12371237
/* Settings
12381238
----------------------------- */
12391239
const getSettings = async () => {
1240-
const [appSettings] = await db.table(Table.SETTINGS).where({ id: 1 });
1241-
return appSettings;
1242-
};
1243-
1244-
const updateSettings = async ({ language, opened, theme }) => {
1240+
return (await db.table(Table.SETTINGS).first('*')) || {};
1241+
};
1242+
1243+
const updateSettings = async ({
1244+
language,
1245+
opened,
1246+
theme,
1247+
autoBackupEnable,
1248+
autoBackupFrequency,
1249+
autoBackupLastDate,
1250+
autoBackupLastSize,
1251+
autoBackupNextDate,
1252+
autoBackupPath
1253+
}) => {
12451254
const params = noNulls({
12461255
language,
12471256
opened,
1248-
theme
1257+
theme,
1258+
autoBackupEnable,
1259+
autoBackupFrequency,
1260+
autoBackupLastDate,
1261+
autoBackupLastSize,
1262+
autoBackupNextDate,
1263+
autoBackupPath
12491264
});
12501265
if (Object.keys(params).length < 1) {
12511266
return Promise.resolve(true);

electron_app/src/Settings.js

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,58 @@ const appSettings = {
33
language: this.language,
44
opened: this.opened,
55
theme: this.theme,
6+
autoBackupEnable: this.autoBackupEnable,
7+
autoBackupFrequency: this.autoBackupFrequency,
8+
autoBackupLastDate: this.autoBackupLastDate,
9+
autoBackupLastSize: this.autoBackupLastSize,
10+
autoBackupNextDate: this.autoBackupNextDate,
11+
autoBackupPath: this.autoBackupPath,
612

7-
initialize({ language, opened, theme, isFromStore }) {
13+
initialize({
14+
language,
15+
opened,
16+
theme,
17+
isFromStore,
18+
autoBackupEnable,
19+
autoBackupFrequency,
20+
autoBackupLastDate,
21+
autoBackupLastSize,
22+
autoBackupNextDate,
23+
autoBackupPath
24+
}) {
825
this.isFromStore = isFromStore;
926
this.language = language;
1027
this.opened = opened;
1128
this.theme = theme;
29+
this.autoBackupEnable = autoBackupEnable;
30+
this.autoBackupFrequency = autoBackupFrequency;
31+
this.autoBackupLastDate = autoBackupLastDate;
32+
this.autoBackupLastSize = autoBackupLastSize;
33+
this.autoBackupNextDate = autoBackupNextDate;
34+
this.autoBackupPath = autoBackupPath;
1235
},
1336

14-
update({ language, opened, theme }) {
37+
update({
38+
language,
39+
opened,
40+
theme,
41+
autoBackupEnable,
42+
autoBackupFrequency,
43+
autoBackupLastDate,
44+
autoBackupLastSize,
45+
autoBackupNextDate,
46+
autoBackupPath
47+
}) {
1548
this.language = language || this.language;
1649
this.opened = opened !== undefined ? opened : this.opened;
1750
this.theme = theme || this.theme;
51+
this.autoBackupEnable =
52+
autoBackupEnable !== undefined ? autoBackupEnable : this.autoBackupEnable;
53+
this.autoBackupFrequency = autoBackupFrequency || this.autoBackupFrequency;
54+
this.autoBackupLastDate = autoBackupLastDate || this.autoBackupLastDate;
55+
this.autoBackupLastSize = autoBackupLastSize || this.autoBackupLastSize;
56+
this.autoBackupNextDate = autoBackupNextDate || this.autoBackupNextDate;
57+
this.autoBackupPath = autoBackupPath || this.autoBackupPath;
1858
}
1959
};
2060

electron_app/src/ipc/backup.js

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
const ipc = require('@criptext/electron-better-ipc');
2+
const { shell } = require('electron');
3+
const moment = require('moment');
4+
const {
5+
createDefaultBackupFolder,
6+
getDefaultBackupFolder,
7+
prepareBackupFiles,
8+
exportBackupUnencrypted,
9+
exportBackupEncrypted,
10+
restoreUnencryptedBackup,
11+
restoreEncryptedBackup
12+
} = require('./../BackupManager');
13+
const globalManager = require('./../globalManager');
14+
const { showNotification } = require('./../notificationManager');
15+
const { sendEventToAllWindows } = require('./../windows/windowUtils');
16+
const {
17+
defineBackupFileName,
18+
defineUnitToAppend,
19+
backupDateFormat
20+
} = require('./../utils/TimeUtils');
21+
const { getSettings, updateSettings } = require('./../DBManager');
22+
global.autoBackupIntervalId = null;
23+
24+
const simulatePause = ms => {
25+
return new Promise(resolve => {
26+
setTimeout(resolve, ms);
27+
});
28+
};
29+
30+
const commitBackupStatus = (eventName, status, params) => {
31+
sendEventToAllWindows(eventName, params);
32+
if (status) globalManager.backupStatus = status;
33+
};
34+
35+
ipc.answerRenderer('create-default-backup-folder', () =>
36+
createDefaultBackupFolder()
37+
);
38+
39+
const doExportBackupUnencrypted = async params => {
40+
const { backupPath, notificationParams } = params;
41+
try {
42+
globalManager.windowsEvents.disable();
43+
commitBackupStatus('local-backup-disable-events', 1);
44+
await prepareBackupFiles({});
45+
await simulatePause(2000);
46+
globalManager.windowsEvents.enable();
47+
commitBackupStatus('local-backup-enable-events', 2);
48+
const backupSize = await exportBackupUnencrypted({ backupPath });
49+
commitBackupStatus('local-backup-export-finished', 3);
50+
await simulatePause(2000);
51+
commitBackupStatus('local-backup-success', null, backupSize);
52+
await simulatePause(2000);
53+
if (notificationParams) {
54+
showNotification({
55+
title: notificationParams.success.title,
56+
message: notificationParams.success.message,
57+
clickHandler: function() {
58+
shell.showItemInFolder(backupPath);
59+
},
60+
forceToShow: true
61+
});
62+
}
63+
return backupSize;
64+
} catch (error) {
65+
globalManager.windowsEvents.enable();
66+
commitBackupStatus('local-backup-enable-events', null, { error });
67+
if (notificationParams) {
68+
showNotification({
69+
title: notificationParams.error.title,
70+
message: notificationParams.error.message,
71+
forceToShow: true
72+
});
73+
}
74+
return 0;
75+
}
76+
};
77+
78+
ipc.answerRenderer('export-backup-unencrypted', doExportBackupUnencrypted);
79+
80+
ipc.answerRenderer('export-backup-encrypted', async params => {
81+
const { backupPath, password, notificationParams } = params;
82+
try {
83+
globalManager.windowsEvents.disable();
84+
commitBackupStatus('local-backup-disable-events', 1);
85+
await prepareBackupFiles({});
86+
await simulatePause(2000);
87+
globalManager.windowsEvents.enable();
88+
commitBackupStatus('local-backup-enable-events', 2);
89+
const backupSize = await exportBackupEncrypted({
90+
backupPath,
91+
password
92+
});
93+
commitBackupStatus('local-backup-export-finished', 3);
94+
await simulatePause(2000);
95+
commitBackupStatus('local-backup-success', null, backupSize);
96+
await simulatePause(2000);
97+
if (notificationParams) {
98+
showNotification({
99+
title: notificationParams.success.title,
100+
message: notificationParams.success.message,
101+
clickHandler: function() {
102+
shell.showItemInFolder(backupPath);
103+
},
104+
forceToShow: true
105+
});
106+
}
107+
return backupSize;
108+
} catch (error) {
109+
globalManager.windowsEvents.enable();
110+
commitBackupStatus('local-backup-enable-events', null, { error });
111+
if (notificationParams) {
112+
showNotification({
113+
title: notificationParams.error.title,
114+
message: notificationParams.error.message,
115+
forceToShow: true
116+
});
117+
}
118+
return 0;
119+
}
120+
});
121+
122+
ipc.answerRenderer('get-default-backup-folder', () => getDefaultBackupFolder());
123+
124+
ipc.answerRenderer('restore-backup-unencrypted', async ({ backupPath }) => {
125+
try {
126+
globalManager.windowsEvents.disable();
127+
commitBackupStatus('restore-backup-disable-events');
128+
await prepareBackupFiles({ backupPrevFiles: false });
129+
await simulatePause(2000);
130+
globalManager.windowsEvents.enable();
131+
commitBackupStatus('restore-backup-enable-events');
132+
await restoreUnencryptedBackup({ filePath: backupPath });
133+
commitBackupStatus('restore-backup-finished');
134+
await simulatePause(2000);
135+
commitBackupStatus('restore-backup-success', null);
136+
} catch (error) {
137+
globalManager.windowsEvents.enable();
138+
commitBackupStatus('restore-backup-enable-events', null, {
139+
error: error.message
140+
});
141+
}
142+
});
143+
144+
ipc.answerRenderer('restore-backup-encrypted', async params => {
145+
const { backupPath, password } = params;
146+
try {
147+
globalManager.windowsEvents.disable();
148+
commitBackupStatus('restore-backup-disable-events');
149+
await prepareBackupFiles({ backupPrevFiles: false });
150+
await simulatePause(2000);
151+
globalManager.windowsEvents.enable();
152+
commitBackupStatus('restore-backup-enable-events');
153+
await restoreEncryptedBackup({ filePath: backupPath, password });
154+
commitBackupStatus('restore-backup-finished');
155+
await simulatePause(2000);
156+
commitBackupStatus('restore-backup-success', null);
157+
} catch (error) {
158+
globalManager.windowsEvents.enable();
159+
commitBackupStatus('restore-backup-enable-events', null, {
160+
error: error.message
161+
});
162+
}
163+
});
164+
165+
const initAutoBackupMonitor = async () => {
166+
clearTimeout(global.autoBackupIntervalId);
167+
const {
168+
autoBackupPath,
169+
autoBackupFrequency,
170+
autoBackupNextDate
171+
} = await getSettings();
172+
if (!autoBackupNextDate) {
173+
log('Failed to get next date');
174+
return;
175+
}
176+
const now = moment();
177+
const pendingDate = moment(autoBackupNextDate);
178+
const timeDiff = pendingDate.diff(now);
179+
180+
global.autoBackupIntervalId = setTimeout(async () => {
181+
try {
182+
const backupFileName = defineBackupFileName('db');
183+
const backupSize = await doExportBackupUnencrypted({
184+
backupPath: `${autoBackupPath}/${backupFileName}`
185+
});
186+
const timeUnit = defineUnitToAppend(autoBackupFrequency);
187+
const today = moment(Date.now());
188+
const nextDate = moment(autoBackupNextDate);
189+
do {
190+
nextDate.add(1, timeUnit);
191+
} while (nextDate.isBefore(today));
192+
await updateSettings({
193+
autoBackupLastDate: pendingDate.format(backupDateFormat),
194+
autoBackupLastSize: backupSize,
195+
autoBackupNextDate: nextDate.format(backupDateFormat)
196+
});
197+
initAutoBackupMonitor();
198+
} catch (backupErr) {
199+
log(
200+
`Failed to do scheduled backup at ${pendingDate.format(
201+
backupDateFormat
202+
)}`
203+
);
204+
}
205+
}, timeDiff);
206+
};
207+
208+
ipc.answerRenderer('init-autobackup-monitor', initAutoBackupMonitor);
209+
210+
const log = message => {
211+
if (process.env.NODE_ENV === 'development') {
212+
console.log(`[AutoBackup]: ${message}`);
213+
}
214+
};
215+
216+
process.on('exit', () => {
217+
global.autoBackupIntervalId = null;
218+
});
219+
220+
module.exports = {
221+
doExportBackupUnencrypted
222+
};

0 commit comments

Comments
 (0)