]> git.wh0rd.org - chrome-ext/tabs-backup.git/blob - background.js
change deleteOldestBackup to promise/async
[chrome-ext/tabs-backup.git] / background.js
1 // Create a backup on first install (or if storage is wiped for some reason.
2 chrome.storage.local.get(function(items) {
3 // Setup defaults.
4 if (items.prefs_max_backup_items === undefined) {
5 chrome.storage.local.set({prefs_max_backup_items: 10});
6 }
7 if (items.prefs_backup_timer === undefined) {
8 chrome.storage.local.set({prefs_backup_timer: 30});
9 }
10
11 // If a backup exists already, nothing to do.
12 if (items.backups_list) {
13 return;
14 }
15
16 // Create a backup now
17 var d = new Date();
18 var formattedDate = date_format (d);
19
20 backupNow(true, formattedDate, function({success, backupName, backupObj}) {
21 // backup completed
22 });
23 });
24
25 function initAlarm () {
26 //console.log("initAlarm");
27
28 var BACKUP_ALARM_NAME = "backup_alarm";
29
30 // Clear any previous alarm
31 chrome.alarms.clearAll();
32
33 chrome.storage.local.get(function(items) {
34 const timerMinutes = items.prefs_backup_timer;
35 chrome.alarms.create(BACKUP_ALARM_NAME, {periodInMinutes: timerMinutes});
36 });
37 }
38
39 initAlarm();
40
41 function onAlarm (alarm) {
42 var d = new Date();
43 var formattedDate = date_format (d);
44
45 console.log("Alarm {" + alarm + "} fired up: " + formattedDate);
46
47 // if last backup time != lastTabsEdit
48 // perform automatic backup
49 backupNow(true, formattedDate, function({success, backupName, backupObj}) {
50 // automatic backup completed
51 chrome.runtime.sendMessage({
52 action: 'insertBackupItem',
53 args: [backupName, backupObj, true /*insertAtBeginning*/, true /*doAnimation*/],
54 });
55 });
56 }
57
58 chrome.alarms.onAlarm.addListener(onAlarm);
59
60 // yyyy-mm-dd hh:mm:ss
61 function date_format(d) {
62 const prependZero = (val) => val.toString().padStart(2, '0');
63 return d.getFullYear() + "-" +
64 prependZero(d.getMonth() + 1) + "-" +
65 prependZero(d.getDate()) + " " +
66 prependZero(d.getHours()) + ":" +
67 prependZero(d.getMinutes()) + ":" +
68 prependZero(d.getSeconds());
69 }
70
71
72 function backupNowManual (callbackDone) {
73 var d = new Date();
74 var formattedDate = date_format (d);
75
76 backupNow(false, formattedDate, callbackDone);
77
78
79 }
80
81 async function deleteOldestBackup() {
82 const items = await chrome.storage.local.get();
83 if (!items.backups_list) {
84 return;
85 }
86
87 const backupsList = items.backups_list;
88 const numItemsToDelete = backupsList.length - items.prefs_max_backup_items;
89 for (let i = 0; i < numItemsToDelete; ++i) {
90 const deletedBackupName = backupsList[i];
91 await deleteBackup(deletedBackupName);
92
93 chrome.runtime.sendMessage({
94 action: 'removeBackupItemDiv',
95 args: [deletedBackupName],
96 });
97 }
98 }
99
100 //var isCreatingBackup = false;
101
102 function backupNow(isAutomatic, backupName, callbackDone) {
103 console.log("backupNow - isAutomatic: " + isAutomatic + " name: " + backupName);
104 /*if (isCreatingBackup === true) {
105 console.log("backupNow - already running..skipping..");
106 return;
107 }*/
108
109 //isCreatingBackup = true;
110
111 /*if (!confirm("Perform a full backup? All windows and their tabs will be saved!")) {
112 return;
113 }*/
114
115 var fullBackup = {
116 windows: [],
117 isAutomatic: isAutomatic,
118 totNumTabs: 0
119 };
120
121 chrome.windows.getAll({populate : true}, function (window_list) {
122 var totNumTabs = 0;
123
124 for(var i=0;i<window_list.length;i++) {
125 var window = window_list[i];
126
127 //console.log ("Window " + i);
128
129 var bkpWindow = {
130 state: window.state,
131 top: window.top,
132 left: window.left,
133 width: window.width,
134 height: window.height,
135 tabs: []
136 };
137
138 var windowTabs = window.tabs;
139
140 // If it's a single window sittig at the new tab page, don't bother
141 // saving it. This is a nice shortcut when things crash as it will
142 // only show a single window.
143 if (windowTabs.length == 1) {
144 const tab = windowTabs[0];
145 if (tab.url == 'chrome://newtab/')
146 continue;
147 }
148
149 for (var j = 0; j < windowTabs.length; j++) {
150 var tab = windowTabs[j];
151
152 //console.log("==> Tab " + j + " (" + tab.index + "): " + tabUrl);
153
154 // Ignore windows that we can't/shouldn't backup.
155 if (tab.url.startsWith('chrome-untrusted://')) {
156 continue;
157 }
158
159 var bkpTab = {
160 url: tab.url,
161 title: tab.title,
162 highlighted: tab.highlighted,
163 pinned: tab.pinned,
164 };
165
166 // Add tab to tabs arrays
167 bkpWindow.tabs.push(bkpTab);
168 }
169
170 if (bkpWindow.tabs.length) {
171 totNumTabs += bkpWindow.tabs.length;
172
173 fullBackup.windows.push(bkpWindow);
174 }
175 }
176
177 if (totNumTabs == 0)
178 return;
179
180 fullBackup.totNumTabs = totNumTabs;
181
182 var storageSetValues = {};
183 storageSetValues[backupName] = fullBackup;
184
185 // Store backup
186 chrome.storage.local.set(storageSetValues, function () {
187 if (chrome.runtime.lastError) {
188 //isCreatingBackup = false;
189 // TODO change icon to error..
190 //alert ("Error: " + chrome.runtime.lastError.message);
191 updateBrowserActionIcon (1);
192
193 callbackDone({success: false});
194 } else {
195 console.log("backup saved");
196 //alert("Backup saved successfully!");
197
198 chrome.storage.local.get(function(items) {
199 var backupsList = [];
200 if(items.backups_list) {
201 backupsList = items.backups_list;
202 }
203
204 console.log("Updating 'backups_list' - cur. size: " + backupsList.length);
205
206 backupsList.push(backupName);
207
208 chrome.storage.local.set({"backups_list": backupsList}, function () {
209 //isCreatingBackup = false;
210
211 if (chrome.runtime.lastError) {
212 console.log ("Error saving backups_list: " + chrome.runtime.lastError.message);
213 updateBrowserActionIcon (1);
214 callbackDone({success: false});
215 } else {
216 console.log("Backups list saved successfully");
217
218 updateBrowserActionIcon (0);
219 callbackDone({
220 success: true,
221 backupName,
222 backupObj: fullBackup,
223 });
224
225 if (backupsList.length > items.prefs_max_backup_items) {
226 deleteOldestBackup();
227 }
228 }
229 });
230 });
231 }
232 });
233 });
234 }
235
236 /**
237 * 0 ==> OK
238 * 1 ==> ERROR
239 */
240 function updateBrowserActionIcon (status) {
241 var icon;
242 switch(status) {
243 case 0:
244 icon = "icon_ok.png";
245 break;
246 default:
247 icon = "icon_error.png";
248 break;
249 }
250
251 chrome.action.setIcon({path: icon});
252 }
253
254 async function deleteBackup(backupName) {
255 console.log("Deleting backup " + backupName);
256
257 await chrome.storage.local.remove(backupName);
258 //console.log("=> Deleted backup " + backupName);
259
260 const items = await chrome.storage.local.get("backups_list");
261 //console.log("==> got backups_list " + backupName);
262
263 if (!items.backups_list) {
264 return;
265 }
266
267 var backupsList = items.backups_list;
268
269 var index = backupsList.indexOf(backupName);
270 if (index >= 0) {
271 backupsList.splice(index, 1);
272 }
273
274 //console.log("===> Updating backups_list (removing " + backupName + ")");
275
276 await chrome.storage.local.set({"backups_list": backupsList});
277 //console.log("===> Updated backups_list (removed " + backupName + ")");
278 }
279
280 function restoreNow(backupName) {
281 console.log("restoreNow");
282
283 chrome.storage.local.get(backupName, function(items) {
284 if(!items[backupName]) {
285 alert("No Backup found");
286 return;
287 }
288
289 /*if (!confirm("Restore full backup?")) {
290 return;
291 }*/
292 /*
293 if (confirm("Would you like to close all existing windows first?")) {
294 chrome.windows.getAll({populate : false}, function (window_list) {
295 for(var i=0;i<window_list.length;i++) {
296 var window = window_list[i];
297 chrome.windows.remove(window.id);
298 }
299 });
300 }*/
301
302
303 var fullBackup = items[backupName];
304
305 for(var i=0;i<fullBackup.windows.length;i++) {
306 const window = fullBackup.windows[i];
307
308 //console.log ("Window " + i);
309
310 urlsToOpen = [];
311
312 const windowTabs = window.tabs;
313 for (let j = 0; j < windowTabs.length; j++) {
314 const tab = windowTabs[j];
315 const tabUrl = tab.url;
316 urlsToOpen.push(tabUrl);
317 }
318
319 const windowProperties = {
320 state: 'normal',
321 url: urlsToOpen,
322 top: window.top,
323 left: window.left,
324 width: window.width,
325 height: window.height,
326 };
327
328 // Create a new Window
329 chrome.windows.create(windowProperties, function(createdWindow) {
330 // Chrome errors if the dimensions are set on non-normal windows.
331 // So we create the window first with the right settings, then
332 // update the window state.
333 if (window.state != 'normal') {
334 chrome.windows.update(createdWindow.id, {state: window.state});
335 }
336
337 chrome.windows.get(createdWindow.id, {populate: true}, ({tabs}) => {
338 for (let tabi = 0; tabi < windowTabs.length; ++tabi) {
339 const oldtab = windowTabs[tabi];
340 const newtab = tabs[tabi];
341 chrome.tabs.update(newtab.id, {
342 highlighted: oldtab.highlighted,
343 pinned: oldtab.pinned,
344 }, () => {
345 if (!oldtab.highlighted) {
346 // If we discard a tab too fast, Chrome will completely
347 // throw it away. Wait until it's in a stable enough
348 // state for us to discard it.
349 let retryCount = 60;
350 const checktab = (id) => {
351 if (retryCount-- < 0)
352 return;
353 chrome.tabs.get(id, (tab) => {
354 if (tab.pendingUrl)
355 setTimeout(() => checktab(id), 500);
356 else
357 chrome.tabs.discard(id);
358 });
359 };
360 checktab(newtab.id);
361 }
362 });
363 }
364 });
365 });
366 }
367 });
368 }
369
370 /**
371 * Callback from other pages (like the popup).
372 */
373 chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
374 console.log(`Got message from ${sender.id}: action=${request.action}`, request);
375
376 let asyncResponse = false;
377 switch (request?.action) {
378 case 'initAlarm':
379 initAlarm();
380 break;
381
382 case 'restoreNow':
383 restoreNow(...request.args);
384 break;
385
386 case 'deleteBackup':
387 deleteBackup(...request.args).then(sendResponse);
388 asyncResponse = true;
389 break;
390
391 case 'backupNowManual':
392 backupNowManual(sendResponse);
393 asyncResponse = true;
394 break;
395 }
396 return asyncResponse;
397 });