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