]>
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 | ||
71208cf4 MF |
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 | } | |
70c1267e MF |
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 = { | |
4fe91aa7 MF |
130 | state: window.state, |
131 | top: window.top, | |
132 | left: window.left, | |
133 | width: window.width, | |
134 | height: window.height, | |
70c1267e MF |
135 | tabs: [] |
136 | }; | |
137 | ||
138 | var windowTabs = window.tabs; | |
4fe91aa7 MF |
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]; | |
3b8161c3 | 145 | if (tab.url == 'chrome://newtab/') |
4fe91aa7 MF |
146 | continue; |
147 | } | |
148 | ||
70c1267e MF |
149 | for (var j = 0; j < windowTabs.length; j++) { |
150 | var tab = windowTabs[j]; | |
151 | ||
152 | //console.log("==> Tab " + j + " (" + tab.index + "): " + tabUrl); | |
153 | ||
e11f3338 MF |
154 | // Ignore windows that we can't/shouldn't backup. |
155 | if (tab.url.startsWith('chrome-untrusted://')) { | |
156 | continue; | |
157 | } | |
158 | ||
70c1267e MF |
159 | var bkpTab = { |
160 | url: tab.url, | |
4fe91aa7 MF |
161 | title: tab.title, |
162 | highlighted: tab.highlighted, | |
163 | pinned: tab.pinned, | |
70c1267e MF |
164 | }; |
165 | ||
166 | // Add tab to tabs arrays | |
167 | bkpWindow.tabs.push(bkpTab); | |
168 | } | |
169 | ||
e11f3338 MF |
170 | if (bkpWindow.tabs.length) { |
171 | totNumTabs += bkpWindow.tabs.length; | |
70c1267e | 172 | |
e11f3338 MF |
173 | fullBackup.windows.push(bkpWindow); |
174 | } | |
70c1267e MF |
175 | } |
176 | ||
e0b6a877 MF |
177 | if (totNumTabs == 0) |
178 | return; | |
179 | ||
70c1267e MF |
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 | ||
eda98b5f | 193 | callbackDone({success: false}); |
70c1267e MF |
194 | } else { |
195 | console.log("backup saved"); | |
196 | //alert("Backup saved successfully!"); | |
197 | ||
d9e77fbf | 198 | chrome.storage.local.get(function(items) { |
70c1267e MF |
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); | |
eda98b5f | 214 | callbackDone({success: false}); |
70c1267e MF |
215 | } else { |
216 | console.log("Backups list saved successfully"); | |
217 | ||
218 | updateBrowserActionIcon (0); | |
eda98b5f MF |
219 | callbackDone({ |
220 | success: true, | |
221 | backupName, | |
222 | backupObj: fullBackup, | |
223 | }); | |
70c1267e | 224 | |
d9e77fbf | 225 | if (backupsList.length > items.prefs_max_backup_items) { |
70c1267e MF |
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 | ||
1f779f9c | 251 | chrome.action.setIcon({path: icon}); |
70c1267e MF |
252 | } |
253 | ||
bc29ea6c | 254 | async function deleteBackup(backupName) { |
70c1267e MF |
255 | console.log("Deleting backup " + backupName); |
256 | ||
bc29ea6c MF |
257 | await chrome.storage.local.remove(backupName); |
258 | //console.log("=> Deleted backup " + backupName); | |
70c1267e | 259 | |
bc29ea6c MF |
260 | const items = await chrome.storage.local.get("backups_list"); |
261 | //console.log("==> got backups_list " + backupName); | |
70c1267e | 262 | |
bc29ea6c MF |
263 | if (!items.backups_list) { |
264 | return; | |
265 | } | |
70c1267e | 266 | |
bc29ea6c | 267 | var backupsList = items.backups_list; |
70c1267e | 268 | |
bc29ea6c MF |
269 | var index = backupsList.indexOf(backupName); |
270 | if (index >= 0) { | |
271 | backupsList.splice(index, 1); | |
272 | } | |
70c1267e | 273 | |
bc29ea6c | 274 | //console.log("===> Updating backups_list (removing " + backupName + ")"); |
70c1267e | 275 | |
bc29ea6c MF |
276 | await chrome.storage.local.set({"backups_list": backupsList}); |
277 | //console.log("===> Updated backups_list (removed " + backupName + ")"); | |
70c1267e MF |
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++) { | |
4fe91aa7 | 306 | const window = fullBackup.windows[i]; |
70c1267e MF |
307 | |
308 | //console.log ("Window " + i); | |
309 | ||
310 | urlsToOpen = []; | |
311 | ||
4fe91aa7 MF |
312 | const windowTabs = window.tabs; |
313 | for (let j = 0; j < windowTabs.length; j++) { | |
314 | const tab = windowTabs[j]; | |
315 | const tabUrl = tab.url; | |
70c1267e MF |
316 | urlsToOpen.push(tabUrl); |
317 | } | |
318 | ||
4fe91aa7 MF |
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, | |
70c1267e MF |
326 | }; |
327 | ||
328 | // Create a new Window | |
329 | chrome.windows.create(windowProperties, function(createdWindow) { | |
4fe91aa7 MF |
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 | } | |
70c1267e | 336 | |
4fe91aa7 MF |
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 | }); | |
70c1267e | 365 | }); |
70c1267e MF |
366 | } |
367 | }); | |
e0b6a877 | 368 | } |
eda98b5f MF |
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': | |
bc29ea6c | 387 | deleteBackup(...request.args).then(sendResponse); |
eda98b5f MF |
388 | asyncResponse = true; |
389 | break; | |
390 | ||
391 | case 'backupNowManual': | |
392 | backupNowManual(sendResponse); | |
393 | asyncResponse = true; | |
394 | break; | |
395 | } | |
396 | return asyncResponse; | |
397 | }); |