]> git.wh0rd.org - chrome-ext/tabs-backup.git/blame - background.js
make it mine
[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
125 deleteBackup (backupsList[i], loopFunc);
126 i++;
127 };
128
129 loopFunc ();
130 }
131
132 //for (var i = 0; i < numItemsToDelete; i++) {
133 // TODO WARNING: I'm calling deleteBackup rapidly, while deleting is async...(I should wait for each delete to complete before deleting the next)
134 //deleteBackup (backupsList[i], function() {
135
136 //});
137 //}
138
139 });
140}
141
142//var isCreatingBackup = false;
143
144function backupNow(isAutomatic, backupName, callbackDone) {
145 console.log("backupNow - isAutomatic: " + isAutomatic + " name: " + backupName);
146 /*if (isCreatingBackup === true) {
147 console.log("backupNow - already running..skipping..");
148 return;
149 }*/
150
151 //isCreatingBackup = true;
152
153 /*if (!confirm("Perform a full backup? All windows and their tabs will be saved!")) {
154 return;
155 }*/
156
157 var fullBackup = {
158 windows: [],
159 isAutomatic: isAutomatic,
160 totNumTabs: 0
161 };
162
163 chrome.windows.getAll({populate : true}, function (window_list) {
164 var totNumTabs = 0;
165
166 for(var i=0;i<window_list.length;i++) {
167 var window = window_list[i];
168
169 //console.log ("Window " + i);
170
171 var bkpWindow = {
4fe91aa7
MF
172 state: window.state,
173 top: window.top,
174 left: window.left,
175 width: window.width,
176 height: window.height,
70c1267e
MF
177 tabs: []
178 };
179
180 var windowTabs = window.tabs;
4fe91aa7
MF
181
182 // If it's a single window sittig at the new tab page, don't bother
183 // saving it. This is a nice shortcut when things crash as it will
184 // only show a single window.
185 if (windowTabs.length == 1) {
186 const tab = windowTabs[0];
3b8161c3 187 if (tab.url == 'chrome://newtab/')
4fe91aa7
MF
188 continue;
189 }
190
70c1267e
MF
191 for (var j = 0; j < windowTabs.length; j++) {
192 var tab = windowTabs[j];
193
194 //console.log("==> Tab " + j + " (" + tab.index + "): " + tabUrl);
195
196 var bkpTab = {
197 url: tab.url,
4fe91aa7
MF
198 title: tab.title,
199 highlighted: tab.highlighted,
200 pinned: tab.pinned,
70c1267e
MF
201 };
202
203 // Add tab to tabs arrays
204 bkpWindow.tabs.push(bkpTab);
205 }
206
207 totNumTabs += windowTabs.length;
208
209 fullBackup.windows.push(bkpWindow);
210 }
211
e0b6a877
MF
212 if (totNumTabs == 0)
213 return;
214
70c1267e
MF
215 fullBackup.totNumTabs = totNumTabs;
216
217 var storageSetValues = {};
218 storageSetValues[backupName] = fullBackup;
219
220 // Store backup
221 chrome.storage.local.set(storageSetValues, function () {
222 if (chrome.runtime.lastError) {
223 //isCreatingBackup = false;
224 // TODO change icon to error..
225 //alert ("Error: " + chrome.runtime.lastError.message);
226 updateBrowserActionIcon (1);
227
eda98b5f 228 callbackDone({success: false});
70c1267e
MF
229 } else {
230 console.log("backup saved");
231 //alert("Backup saved successfully!");
232
d9e77fbf 233 chrome.storage.local.get(function(items) {
70c1267e
MF
234 var backupsList = [];
235 if(items.backups_list) {
236 backupsList = items.backups_list;
237 }
238
239 console.log("Updating 'backups_list' - cur. size: " + backupsList.length);
240
241 backupsList.push(backupName);
242
243 chrome.storage.local.set({"backups_list": backupsList}, function () {
244 //isCreatingBackup = false;
245
246 if (chrome.runtime.lastError) {
247 console.log ("Error saving backups_list: " + chrome.runtime.lastError.message);
248 updateBrowserActionIcon (1);
eda98b5f 249 callbackDone({success: false});
70c1267e
MF
250 } else {
251 console.log("Backups list saved successfully");
252
253 updateBrowserActionIcon (0);
eda98b5f
MF
254 callbackDone({
255 success: true,
256 backupName,
257 backupObj: fullBackup,
258 });
70c1267e 259
d9e77fbf 260 if (backupsList.length > items.prefs_max_backup_items) {
70c1267e
MF
261 deleteOldestBackup();
262 }
263 }
264 });
265 });
266 }
267 });
268 });
269}
270
271/**
272 * 0 ==> OK
273 * 1 ==> ERROR
274 */
275function updateBrowserActionIcon (status) {
276 var icon;
277 switch(status) {
278 case 0:
279 icon = "icon_ok.png";
280 break;
281 default:
282 icon = "icon_error.png";
283 break;
284 }
285
1f779f9c 286 chrome.action.setIcon({path: icon});
70c1267e
MF
287}
288
289function deleteBackup (backupName, callback) {
290 console.log("Deleting backup " + backupName);
291
292 chrome.storage.local.remove(backupName, function() {
293 //console.log ("=> Deleted backup " + backupName);
294
295 chrome.storage.local.get("backups_list", function(items) {
296 //console.log ("==> got backups_list " + backupName);
297
298 if(!items.backups_list) {
299 callback();
300 return;
301 }
302
303 var backupsList = items.backups_list;
304
305 var index = backupsList.indexOf(backupName);
306 if (index >= 0) {
307 backupsList.splice(index, 1);
308 }
309
310 //console.log ("===> Updating backups_list (removing " + backupName + ")");
311
312 chrome.storage.local.set({"backups_list": backupsList}, function() {
313 //console.log ("===> Updated backups_list (removed " + backupName + ")");
314
315 callback();
316 });
317
318 //console.log ("==> EXIT got backups_list " + backupName);
319 });
320
321 //console.log ("=> EXIT Deleted backup " + backupName);
322 });
323
324 //console.log("EXIT Deleting backup " + backupName);
325
326
327}
328
329function restoreNow(backupName) {
330 console.log("restoreNow");
331
332 chrome.storage.local.get(backupName, function(items) {
333 if(!items[backupName]) {
334 alert("No Backup found");
335 return;
336 }
337
338 /*if (!confirm("Restore full backup?")) {
339 return;
340 }*/
341 /*
342 if (confirm("Would you like to close all existing windows first?")) {
343 chrome.windows.getAll({populate : false}, function (window_list) {
344 for(var i=0;i<window_list.length;i++) {
345 var window = window_list[i];
346 chrome.windows.remove(window.id);
347 }
348 });
349 }*/
350
351
352 var fullBackup = items[backupName];
353
354 for(var i=0;i<fullBackup.windows.length;i++) {
4fe91aa7 355 const window = fullBackup.windows[i];
70c1267e
MF
356
357 //console.log ("Window " + i);
358
359 urlsToOpen = [];
360
4fe91aa7
MF
361 const windowTabs = window.tabs;
362 for (let j = 0; j < windowTabs.length; j++) {
363 const tab = windowTabs[j];
364 const tabUrl = tab.url;
70c1267e
MF
365 urlsToOpen.push(tabUrl);
366 }
367
4fe91aa7
MF
368 const windowProperties = {
369 state: 'normal',
370 url: urlsToOpen,
371 top: window.top,
372 left: window.left,
373 width: window.width,
374 height: window.height,
70c1267e
MF
375 };
376
377 // Create a new Window
378 chrome.windows.create(windowProperties, function(createdWindow) {
4fe91aa7
MF
379 // Chrome errors if the dimensions are set on non-normal windows.
380 // So we create the window first with the right settings, then
381 // update the window state.
382 if (window.state != 'normal') {
383 chrome.windows.update(createdWindow.id, {state: window.state});
384 }
70c1267e 385
4fe91aa7
MF
386 chrome.windows.get(createdWindow.id, {populate: true}, ({tabs}) => {
387 for (let tabi = 0; tabi < windowTabs.length; ++tabi) {
388 const oldtab = windowTabs[tabi];
389 const newtab = tabs[tabi];
390 chrome.tabs.update(newtab.id, {
391 highlighted: oldtab.highlighted,
392 pinned: oldtab.pinned,
393 }, () => {
394 if (!oldtab.highlighted) {
395 // If we discard a tab too fast, Chrome will completely
396 // throw it away. Wait until it's in a stable enough
397 // state for us to discard it.
398 let retryCount = 60;
399 const checktab = (id) => {
400 if (retryCount-- < 0)
401 return;
402 chrome.tabs.get(id, (tab) => {
403 if (tab.pendingUrl)
404 setTimeout(() => checktab(id), 500);
405 else
406 chrome.tabs.discard(id);
407 });
408 };
409 checktab(newtab.id);
410 }
411 });
412 }
413 });
70c1267e 414 });
70c1267e
MF
415 }
416 });
e0b6a877 417}
eda98b5f
MF
418
419/**
420 * Callback from other pages (like the popup).
421 */
422chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
423 console.log(`Got message from ${sender.id}: action=${request.action}`, request);
424
425 let asyncResponse = false;
426 switch (request?.action) {
427 case 'initAlarm':
428 initAlarm();
429 break;
430
431 case 'restoreNow':
432 restoreNow(...request.args);
433 break;
434
435 case 'deleteBackup':
436 deleteBackup(...request.args, sendResponse);
437 asyncResponse = true;
438 break;
439
440 case 'backupNowManual':
441 backupNowManual(sendResponse);
442 asyncResponse = true;
443 break;
444 }
445 return asyncResponse;
446});