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