Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
michael@0 | 2 | * vim: sw=2 ts=2 sts=2 expandtab filetype=javascript |
michael@0 | 3 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | const Cc = Components.classes; |
michael@0 | 8 | const Ci = Components.interfaces; |
michael@0 | 9 | const Cr = Components.results; |
michael@0 | 10 | const Cu = Components.utils; |
michael@0 | 11 | |
michael@0 | 12 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 13 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/PlacesUtils.jsm"); |
michael@0 | 15 | |
michael@0 | 16 | this.EXPORTED_SYMBOLS = [ "PlacesDBUtils" ]; |
michael@0 | 17 | |
michael@0 | 18 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 19 | //// Constants |
michael@0 | 20 | |
michael@0 | 21 | const FINISHED_MAINTENANCE_TOPIC = "places-maintenance-finished"; |
michael@0 | 22 | |
michael@0 | 23 | const BYTES_PER_MEBIBYTE = 1048576; |
michael@0 | 24 | |
michael@0 | 25 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 26 | //// Smart getters |
michael@0 | 27 | |
michael@0 | 28 | XPCOMUtils.defineLazyGetter(this, "DBConn", function() { |
michael@0 | 29 | return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; |
michael@0 | 30 | }); |
michael@0 | 31 | |
michael@0 | 32 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 33 | //// PlacesDBUtils |
michael@0 | 34 | |
michael@0 | 35 | this.PlacesDBUtils = { |
michael@0 | 36 | /** |
michael@0 | 37 | * Executes a list of maintenance tasks. |
michael@0 | 38 | * Once finished it will pass a array log to the callback attached to tasks. |
michael@0 | 39 | * FINISHED_MAINTENANCE_TOPIC is notified through observer service on finish. |
michael@0 | 40 | * |
michael@0 | 41 | * @param aTasks |
michael@0 | 42 | * Tasks object to execute. |
michael@0 | 43 | */ |
michael@0 | 44 | _executeTasks: function PDBU__executeTasks(aTasks) |
michael@0 | 45 | { |
michael@0 | 46 | if (PlacesDBUtils._isShuttingDown) { |
michael@0 | 47 | aTasks.log("- We are shutting down. Will not schedule the tasks."); |
michael@0 | 48 | aTasks.clear(); |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | let task = aTasks.pop(); |
michael@0 | 52 | if (task) { |
michael@0 | 53 | task.call(PlacesDBUtils, aTasks); |
michael@0 | 54 | } |
michael@0 | 55 | else { |
michael@0 | 56 | // All tasks have been completed. |
michael@0 | 57 | // Telemetry the time it took for maintenance, if a start time exists. |
michael@0 | 58 | if (aTasks._telemetryStart) { |
michael@0 | 59 | Services.telemetry.getHistogramById("PLACES_IDLE_MAINTENANCE_TIME_MS") |
michael@0 | 60 | .add(Date.now() - aTasks._telemetryStart); |
michael@0 | 61 | aTasks._telemetryStart = 0; |
michael@0 | 62 | } |
michael@0 | 63 | |
michael@0 | 64 | if (aTasks.callback) { |
michael@0 | 65 | let scope = aTasks.scope || Cu.getGlobalForObject(aTasks.callback); |
michael@0 | 66 | aTasks.callback.call(scope, aTasks.messages); |
michael@0 | 67 | } |
michael@0 | 68 | |
michael@0 | 69 | // Notify observers that maintenance finished. |
michael@0 | 70 | Services.prefs.setIntPref("places.database.lastMaintenance", parseInt(Date.now() / 1000)); |
michael@0 | 71 | Services.obs.notifyObservers(null, FINISHED_MAINTENANCE_TOPIC, null); |
michael@0 | 72 | } |
michael@0 | 73 | }, |
michael@0 | 74 | |
michael@0 | 75 | _isShuttingDown : false, |
michael@0 | 76 | shutdown: function PDBU_shutdown() { |
michael@0 | 77 | PlacesDBUtils._isShuttingDown = true; |
michael@0 | 78 | }, |
michael@0 | 79 | |
michael@0 | 80 | /** |
michael@0 | 81 | * Executes integrity check and common maintenance tasks. |
michael@0 | 82 | * |
michael@0 | 83 | * @param [optional] aCallback |
michael@0 | 84 | * Callback to be invoked when done. The callback will get a array |
michael@0 | 85 | * of log messages. |
michael@0 | 86 | * @param [optional] aScope |
michael@0 | 87 | * Scope for the callback. |
michael@0 | 88 | */ |
michael@0 | 89 | maintenanceOnIdle: function PDBU_maintenanceOnIdle(aCallback, aScope) |
michael@0 | 90 | { |
michael@0 | 91 | let tasks = new Tasks([ |
michael@0 | 92 | this.checkIntegrity |
michael@0 | 93 | , this.checkCoherence |
michael@0 | 94 | , this._refreshUI |
michael@0 | 95 | ]); |
michael@0 | 96 | tasks._telemetryStart = Date.now(); |
michael@0 | 97 | tasks.callback = aCallback; |
michael@0 | 98 | tasks.scope = aScope; |
michael@0 | 99 | this._executeTasks(tasks); |
michael@0 | 100 | }, |
michael@0 | 101 | |
michael@0 | 102 | /** |
michael@0 | 103 | * Executes integrity check, common and advanced maintenance tasks (like |
michael@0 | 104 | * expiration and vacuum). Will also collect statistics on the database. |
michael@0 | 105 | * |
michael@0 | 106 | * @param [optional] aCallback |
michael@0 | 107 | * Callback to be invoked when done. The callback will get a array |
michael@0 | 108 | * of log messages. |
michael@0 | 109 | * @param [optional] aScope |
michael@0 | 110 | * Scope for the callback. |
michael@0 | 111 | */ |
michael@0 | 112 | checkAndFixDatabase: function PDBU_checkAndFixDatabase(aCallback, aScope) |
michael@0 | 113 | { |
michael@0 | 114 | let tasks = new Tasks([ |
michael@0 | 115 | this.checkIntegrity |
michael@0 | 116 | , this.checkCoherence |
michael@0 | 117 | , this.expire |
michael@0 | 118 | , this.vacuum |
michael@0 | 119 | , this.stats |
michael@0 | 120 | , this._refreshUI |
michael@0 | 121 | ]); |
michael@0 | 122 | tasks.callback = aCallback; |
michael@0 | 123 | tasks.scope = aScope; |
michael@0 | 124 | this._executeTasks(tasks); |
michael@0 | 125 | }, |
michael@0 | 126 | |
michael@0 | 127 | /** |
michael@0 | 128 | * Forces a full refresh of Places views. |
michael@0 | 129 | * |
michael@0 | 130 | * @param [optional] aTasks |
michael@0 | 131 | * Tasks object to execute. |
michael@0 | 132 | */ |
michael@0 | 133 | _refreshUI: function PDBU__refreshUI(aTasks) |
michael@0 | 134 | { |
michael@0 | 135 | let tasks = new Tasks(aTasks); |
michael@0 | 136 | |
michael@0 | 137 | // Send batch update notifications to update the UI. |
michael@0 | 138 | PlacesUtils.history.runInBatchMode({ |
michael@0 | 139 | runBatched: function (aUserData) {} |
michael@0 | 140 | }, null); |
michael@0 | 141 | PlacesDBUtils._executeTasks(tasks); |
michael@0 | 142 | }, |
michael@0 | 143 | |
michael@0 | 144 | _handleError: function PDBU__handleError(aError) |
michael@0 | 145 | { |
michael@0 | 146 | Cu.reportError("Async statement execution returned with '" + |
michael@0 | 147 | aError.result + "', '" + aError.message + "'"); |
michael@0 | 148 | }, |
michael@0 | 149 | |
michael@0 | 150 | /** |
michael@0 | 151 | * Tries to execute a REINDEX on the database. |
michael@0 | 152 | * |
michael@0 | 153 | * @param [optional] aTasks |
michael@0 | 154 | * Tasks object to execute. |
michael@0 | 155 | */ |
michael@0 | 156 | reindex: function PDBU_reindex(aTasks) |
michael@0 | 157 | { |
michael@0 | 158 | let tasks = new Tasks(aTasks); |
michael@0 | 159 | tasks.log("> Reindex"); |
michael@0 | 160 | |
michael@0 | 161 | let stmt = DBConn.createAsyncStatement("REINDEX"); |
michael@0 | 162 | stmt.executeAsync({ |
michael@0 | 163 | handleError: PlacesDBUtils._handleError, |
michael@0 | 164 | handleResult: function () {}, |
michael@0 | 165 | |
michael@0 | 166 | handleCompletion: function (aReason) |
michael@0 | 167 | { |
michael@0 | 168 | if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { |
michael@0 | 169 | tasks.log("+ The database has been reindexed"); |
michael@0 | 170 | } |
michael@0 | 171 | else { |
michael@0 | 172 | tasks.log("- Unable to reindex database"); |
michael@0 | 173 | } |
michael@0 | 174 | |
michael@0 | 175 | PlacesDBUtils._executeTasks(tasks); |
michael@0 | 176 | } |
michael@0 | 177 | }); |
michael@0 | 178 | stmt.finalize(); |
michael@0 | 179 | }, |
michael@0 | 180 | |
michael@0 | 181 | /** |
michael@0 | 182 | * Checks integrity but does not try to fix the database through a reindex. |
michael@0 | 183 | * |
michael@0 | 184 | * @param [optional] aTasks |
michael@0 | 185 | * Tasks object to execute. |
michael@0 | 186 | */ |
michael@0 | 187 | _checkIntegritySkipReindex: function PDBU__checkIntegritySkipReindex(aTasks) |
michael@0 | 188 | this.checkIntegrity(aTasks, true), |
michael@0 | 189 | |
michael@0 | 190 | /** |
michael@0 | 191 | * Checks integrity and tries to fix the database through a reindex. |
michael@0 | 192 | * |
michael@0 | 193 | * @param [optional] aTasks |
michael@0 | 194 | * Tasks object to execute. |
michael@0 | 195 | * @param [optional] aSkipdReindex |
michael@0 | 196 | * Whether to try to reindex database or not. |
michael@0 | 197 | */ |
michael@0 | 198 | checkIntegrity: function PDBU_checkIntegrity(aTasks, aSkipReindex) |
michael@0 | 199 | { |
michael@0 | 200 | let tasks = new Tasks(aTasks); |
michael@0 | 201 | tasks.log("> Integrity check"); |
michael@0 | 202 | |
michael@0 | 203 | // Run a integrity check, but stop at the first error. |
michael@0 | 204 | let stmt = DBConn.createAsyncStatement("PRAGMA integrity_check(1)"); |
michael@0 | 205 | stmt.executeAsync({ |
michael@0 | 206 | handleError: PlacesDBUtils._handleError, |
michael@0 | 207 | |
michael@0 | 208 | _corrupt: false, |
michael@0 | 209 | handleResult: function (aResultSet) |
michael@0 | 210 | { |
michael@0 | 211 | let row = aResultSet.getNextRow(); |
michael@0 | 212 | this._corrupt = row.getResultByIndex(0) != "ok"; |
michael@0 | 213 | }, |
michael@0 | 214 | |
michael@0 | 215 | handleCompletion: function (aReason) |
michael@0 | 216 | { |
michael@0 | 217 | if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { |
michael@0 | 218 | if (this._corrupt) { |
michael@0 | 219 | tasks.log("- The database is corrupt"); |
michael@0 | 220 | if (aSkipReindex) { |
michael@0 | 221 | tasks.log("- Unable to fix corruption, database will be replaced on next startup"); |
michael@0 | 222 | Services.prefs.setBoolPref("places.database.replaceOnStartup", true); |
michael@0 | 223 | tasks.clear(); |
michael@0 | 224 | } |
michael@0 | 225 | else { |
michael@0 | 226 | // Try to reindex, this often fixed simple indices corruption. |
michael@0 | 227 | // We insert from the top of the queue, they will run inverse. |
michael@0 | 228 | tasks.push(PlacesDBUtils._checkIntegritySkipReindex); |
michael@0 | 229 | tasks.push(PlacesDBUtils.reindex); |
michael@0 | 230 | } |
michael@0 | 231 | } |
michael@0 | 232 | else { |
michael@0 | 233 | tasks.log("+ The database is sane"); |
michael@0 | 234 | } |
michael@0 | 235 | } |
michael@0 | 236 | else { |
michael@0 | 237 | tasks.log("- Unable to check database status"); |
michael@0 | 238 | tasks.clear(); |
michael@0 | 239 | } |
michael@0 | 240 | |
michael@0 | 241 | PlacesDBUtils._executeTasks(tasks); |
michael@0 | 242 | } |
michael@0 | 243 | }); |
michael@0 | 244 | stmt.finalize(); |
michael@0 | 245 | }, |
michael@0 | 246 | |
michael@0 | 247 | /** |
michael@0 | 248 | * Checks data coherence and tries to fix most common errors. |
michael@0 | 249 | * |
michael@0 | 250 | * @param [optional] aTasks |
michael@0 | 251 | * Tasks object to execute. |
michael@0 | 252 | */ |
michael@0 | 253 | checkCoherence: function PDBU_checkCoherence(aTasks) |
michael@0 | 254 | { |
michael@0 | 255 | let tasks = new Tasks(aTasks); |
michael@0 | 256 | tasks.log("> Coherence check"); |
michael@0 | 257 | |
michael@0 | 258 | let stmts = PlacesDBUtils._getBoundCoherenceStatements(); |
michael@0 | 259 | DBConn.executeAsync(stmts, stmts.length, { |
michael@0 | 260 | handleError: PlacesDBUtils._handleError, |
michael@0 | 261 | handleResult: function () {}, |
michael@0 | 262 | |
michael@0 | 263 | handleCompletion: function (aReason) |
michael@0 | 264 | { |
michael@0 | 265 | if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { |
michael@0 | 266 | tasks.log("+ The database is coherent"); |
michael@0 | 267 | } |
michael@0 | 268 | else { |
michael@0 | 269 | tasks.log("- Unable to check database coherence"); |
michael@0 | 270 | tasks.clear(); |
michael@0 | 271 | } |
michael@0 | 272 | |
michael@0 | 273 | PlacesDBUtils._executeTasks(tasks); |
michael@0 | 274 | } |
michael@0 | 275 | }); |
michael@0 | 276 | stmts.forEach(function (aStmt) aStmt.finalize()); |
michael@0 | 277 | }, |
michael@0 | 278 | |
michael@0 | 279 | _getBoundCoherenceStatements: function PDBU__getBoundCoherenceStatements() |
michael@0 | 280 | { |
michael@0 | 281 | let cleanupStatements = []; |
michael@0 | 282 | |
michael@0 | 283 | // MOZ_ANNO_ATTRIBUTES |
michael@0 | 284 | // A.1 remove obsolete annotations from moz_annos. |
michael@0 | 285 | // The 'weave0' idiom exploits character ordering (0 follows /) to |
michael@0 | 286 | // efficiently select all annos with a 'weave/' prefix. |
michael@0 | 287 | let deleteObsoleteAnnos = DBConn.createAsyncStatement( |
michael@0 | 288 | "DELETE FROM moz_annos " + |
michael@0 | 289 | "WHERE anno_attribute_id IN ( " + |
michael@0 | 290 | " SELECT id FROM moz_anno_attributes " + |
michael@0 | 291 | " WHERE name BETWEEN 'weave/' AND 'weave0' " + |
michael@0 | 292 | ")"); |
michael@0 | 293 | cleanupStatements.push(deleteObsoleteAnnos); |
michael@0 | 294 | |
michael@0 | 295 | // A.2 remove obsolete annotations from moz_items_annos. |
michael@0 | 296 | let deleteObsoleteItemsAnnos = DBConn.createAsyncStatement( |
michael@0 | 297 | "DELETE FROM moz_items_annos " + |
michael@0 | 298 | "WHERE anno_attribute_id IN ( " + |
michael@0 | 299 | " SELECT id FROM moz_anno_attributes " + |
michael@0 | 300 | " WHERE name = 'sync/children' " + |
michael@0 | 301 | " OR name = 'placesInternal/GUID' " + |
michael@0 | 302 | " OR name BETWEEN 'weave/' AND 'weave0' " + |
michael@0 | 303 | ")"); |
michael@0 | 304 | cleanupStatements.push(deleteObsoleteItemsAnnos); |
michael@0 | 305 | |
michael@0 | 306 | // A.3 remove unused attributes. |
michael@0 | 307 | let deleteUnusedAnnoAttributes = DBConn.createAsyncStatement( |
michael@0 | 308 | "DELETE FROM moz_anno_attributes WHERE id IN ( " + |
michael@0 | 309 | "SELECT id FROM moz_anno_attributes n " + |
michael@0 | 310 | "WHERE NOT EXISTS " + |
michael@0 | 311 | "(SELECT id FROM moz_annos WHERE anno_attribute_id = n.id LIMIT 1) " + |
michael@0 | 312 | "AND NOT EXISTS " + |
michael@0 | 313 | "(SELECT id FROM moz_items_annos WHERE anno_attribute_id = n.id LIMIT 1) " + |
michael@0 | 314 | ")"); |
michael@0 | 315 | cleanupStatements.push(deleteUnusedAnnoAttributes); |
michael@0 | 316 | |
michael@0 | 317 | // MOZ_ANNOS |
michael@0 | 318 | // B.1 remove annos with an invalid attribute |
michael@0 | 319 | let deleteInvalidAttributeAnnos = DBConn.createAsyncStatement( |
michael@0 | 320 | "DELETE FROM moz_annos WHERE id IN ( " + |
michael@0 | 321 | "SELECT id FROM moz_annos a " + |
michael@0 | 322 | "WHERE NOT EXISTS " + |
michael@0 | 323 | "(SELECT id FROM moz_anno_attributes " + |
michael@0 | 324 | "WHERE id = a.anno_attribute_id LIMIT 1) " + |
michael@0 | 325 | ")"); |
michael@0 | 326 | cleanupStatements.push(deleteInvalidAttributeAnnos); |
michael@0 | 327 | |
michael@0 | 328 | // B.2 remove orphan annos |
michael@0 | 329 | let deleteOrphanAnnos = DBConn.createAsyncStatement( |
michael@0 | 330 | "DELETE FROM moz_annos WHERE id IN ( " + |
michael@0 | 331 | "SELECT id FROM moz_annos a " + |
michael@0 | 332 | "WHERE NOT EXISTS " + |
michael@0 | 333 | "(SELECT id FROM moz_places WHERE id = a.place_id LIMIT 1) " + |
michael@0 | 334 | ")"); |
michael@0 | 335 | cleanupStatements.push(deleteOrphanAnnos); |
michael@0 | 336 | |
michael@0 | 337 | // MOZ_BOOKMARKS_ROOTS |
michael@0 | 338 | // C.1 fix missing Places root |
michael@0 | 339 | // Bug 477739 shows a case where the root could be wrongly removed |
michael@0 | 340 | // due to an endianness issue. We try to fix broken roots here. |
michael@0 | 341 | let selectPlacesRoot = DBConn.createStatement( |
michael@0 | 342 | "SELECT id FROM moz_bookmarks WHERE id = :places_root"); |
michael@0 | 343 | selectPlacesRoot.params["places_root"] = PlacesUtils.placesRootId; |
michael@0 | 344 | if (!selectPlacesRoot.executeStep()) { |
michael@0 | 345 | // We are missing the root, try to recreate it. |
michael@0 | 346 | let createPlacesRoot = DBConn.createAsyncStatement( |
michael@0 | 347 | "INSERT INTO moz_bookmarks (id, type, fk, parent, position, title, " |
michael@0 | 348 | + "guid) " |
michael@0 | 349 | + "VALUES (:places_root, 2, NULL, 0, 0, :title, GENERATE_GUID())"); |
michael@0 | 350 | createPlacesRoot.params["places_root"] = PlacesUtils.placesRootId; |
michael@0 | 351 | createPlacesRoot.params["title"] = ""; |
michael@0 | 352 | cleanupStatements.push(createPlacesRoot); |
michael@0 | 353 | |
michael@0 | 354 | // Now ensure that other roots are children of Places root. |
michael@0 | 355 | let fixPlacesRootChildren = DBConn.createAsyncStatement( |
michael@0 | 356 | "UPDATE moz_bookmarks SET parent = :places_root WHERE id IN " + |
michael@0 | 357 | "(SELECT folder_id FROM moz_bookmarks_roots " + |
michael@0 | 358 | "WHERE folder_id <> :places_root)"); |
michael@0 | 359 | fixPlacesRootChildren.params["places_root"] = PlacesUtils.placesRootId; |
michael@0 | 360 | cleanupStatements.push(fixPlacesRootChildren); |
michael@0 | 361 | } |
michael@0 | 362 | selectPlacesRoot.finalize(); |
michael@0 | 363 | |
michael@0 | 364 | // C.2 fix roots titles |
michael@0 | 365 | // some alpha version has wrong roots title, and this also fixes them if |
michael@0 | 366 | // locale has changed. |
michael@0 | 367 | let updateRootTitleSql = "UPDATE moz_bookmarks SET title = :title " + |
michael@0 | 368 | "WHERE id = :root_id AND title <> :title"; |
michael@0 | 369 | // root |
michael@0 | 370 | let fixPlacesRootTitle = DBConn.createAsyncStatement(updateRootTitleSql); |
michael@0 | 371 | fixPlacesRootTitle.params["root_id"] = PlacesUtils.placesRootId; |
michael@0 | 372 | fixPlacesRootTitle.params["title"] = ""; |
michael@0 | 373 | cleanupStatements.push(fixPlacesRootTitle); |
michael@0 | 374 | // bookmarks menu |
michael@0 | 375 | let fixBookmarksMenuTitle = DBConn.createAsyncStatement(updateRootTitleSql); |
michael@0 | 376 | fixBookmarksMenuTitle.params["root_id"] = PlacesUtils.bookmarksMenuFolderId; |
michael@0 | 377 | fixBookmarksMenuTitle.params["title"] = |
michael@0 | 378 | PlacesUtils.getString("BookmarksMenuFolderTitle"); |
michael@0 | 379 | cleanupStatements.push(fixBookmarksMenuTitle); |
michael@0 | 380 | // bookmarks toolbar |
michael@0 | 381 | let fixBookmarksToolbarTitle = DBConn.createAsyncStatement(updateRootTitleSql); |
michael@0 | 382 | fixBookmarksToolbarTitle.params["root_id"] = PlacesUtils.toolbarFolderId; |
michael@0 | 383 | fixBookmarksToolbarTitle.params["title"] = |
michael@0 | 384 | PlacesUtils.getString("BookmarksToolbarFolderTitle"); |
michael@0 | 385 | cleanupStatements.push(fixBookmarksToolbarTitle); |
michael@0 | 386 | // unsorted bookmarks |
michael@0 | 387 | let fixUnsortedBookmarksTitle = DBConn.createAsyncStatement(updateRootTitleSql); |
michael@0 | 388 | fixUnsortedBookmarksTitle.params["root_id"] = PlacesUtils.unfiledBookmarksFolderId; |
michael@0 | 389 | fixUnsortedBookmarksTitle.params["title"] = |
michael@0 | 390 | PlacesUtils.getString("UnsortedBookmarksFolderTitle"); |
michael@0 | 391 | cleanupStatements.push(fixUnsortedBookmarksTitle); |
michael@0 | 392 | // tags |
michael@0 | 393 | let fixTagsRootTitle = DBConn.createAsyncStatement(updateRootTitleSql); |
michael@0 | 394 | fixTagsRootTitle.params["root_id"] = PlacesUtils.tagsFolderId; |
michael@0 | 395 | fixTagsRootTitle.params["title"] = |
michael@0 | 396 | PlacesUtils.getString("TagsFolderTitle"); |
michael@0 | 397 | cleanupStatements.push(fixTagsRootTitle); |
michael@0 | 398 | |
michael@0 | 399 | // MOZ_BOOKMARKS |
michael@0 | 400 | // D.1 remove items without a valid place |
michael@0 | 401 | // if fk IS NULL we fix them in D.7 |
michael@0 | 402 | let deleteNoPlaceItems = DBConn.createAsyncStatement( |
michael@0 | 403 | "DELETE FROM moz_bookmarks WHERE id NOT IN ( " + |
michael@0 | 404 | "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots |
michael@0 | 405 | ") AND id IN (" + |
michael@0 | 406 | "SELECT b.id FROM moz_bookmarks b " + |
michael@0 | 407 | "WHERE fk NOT NULL AND b.type = :bookmark_type " + |
michael@0 | 408 | "AND NOT EXISTS (SELECT url FROM moz_places WHERE id = b.fk LIMIT 1) " + |
michael@0 | 409 | ")"); |
michael@0 | 410 | deleteNoPlaceItems.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; |
michael@0 | 411 | cleanupStatements.push(deleteNoPlaceItems); |
michael@0 | 412 | |
michael@0 | 413 | // D.2 remove items that are not uri bookmarks from tag containers |
michael@0 | 414 | let deleteBogusTagChildren = DBConn.createAsyncStatement( |
michael@0 | 415 | "DELETE FROM moz_bookmarks WHERE id NOT IN ( " + |
michael@0 | 416 | "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots |
michael@0 | 417 | ") AND id IN (" + |
michael@0 | 418 | "SELECT b.id FROM moz_bookmarks b " + |
michael@0 | 419 | "WHERE b.parent IN " + |
michael@0 | 420 | "(SELECT id FROM moz_bookmarks WHERE parent = :tags_folder) " + |
michael@0 | 421 | "AND b.type <> :bookmark_type " + |
michael@0 | 422 | ")"); |
michael@0 | 423 | deleteBogusTagChildren.params["tags_folder"] = PlacesUtils.tagsFolderId; |
michael@0 | 424 | deleteBogusTagChildren.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; |
michael@0 | 425 | cleanupStatements.push(deleteBogusTagChildren); |
michael@0 | 426 | |
michael@0 | 427 | // D.3 remove empty tags |
michael@0 | 428 | let deleteEmptyTags = DBConn.createAsyncStatement( |
michael@0 | 429 | "DELETE FROM moz_bookmarks WHERE id NOT IN ( " + |
michael@0 | 430 | "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots |
michael@0 | 431 | ") AND id IN (" + |
michael@0 | 432 | "SELECT b.id FROM moz_bookmarks b " + |
michael@0 | 433 | "WHERE b.id IN " + |
michael@0 | 434 | "(SELECT id FROM moz_bookmarks WHERE parent = :tags_folder) " + |
michael@0 | 435 | "AND NOT EXISTS " + |
michael@0 | 436 | "(SELECT id from moz_bookmarks WHERE parent = b.id LIMIT 1) " + |
michael@0 | 437 | ")"); |
michael@0 | 438 | deleteEmptyTags.params["tags_folder"] = PlacesUtils.tagsFolderId; |
michael@0 | 439 | cleanupStatements.push(deleteEmptyTags); |
michael@0 | 440 | |
michael@0 | 441 | // D.4 move orphan items to unsorted folder |
michael@0 | 442 | let fixOrphanItems = DBConn.createAsyncStatement( |
michael@0 | 443 | "UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN ( " + |
michael@0 | 444 | "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots |
michael@0 | 445 | ") AND id IN (" + |
michael@0 | 446 | "SELECT b.id FROM moz_bookmarks b " + |
michael@0 | 447 | "WHERE b.parent <> 0 " + // exclude Places root |
michael@0 | 448 | "AND NOT EXISTS " + |
michael@0 | 449 | "(SELECT id FROM moz_bookmarks WHERE id = b.parent LIMIT 1) " + |
michael@0 | 450 | ")"); |
michael@0 | 451 | fixOrphanItems.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId; |
michael@0 | 452 | cleanupStatements.push(fixOrphanItems); |
michael@0 | 453 | |
michael@0 | 454 | // D.5 fix wrong keywords |
michael@0 | 455 | let fixInvalidKeywords = DBConn.createAsyncStatement( |
michael@0 | 456 | "UPDATE moz_bookmarks SET keyword_id = NULL WHERE id NOT IN ( " + |
michael@0 | 457 | "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots |
michael@0 | 458 | ") AND id IN ( " + |
michael@0 | 459 | "SELECT id FROM moz_bookmarks b " + |
michael@0 | 460 | "WHERE keyword_id NOT NULL " + |
michael@0 | 461 | "AND NOT EXISTS " + |
michael@0 | 462 | "(SELECT id FROM moz_keywords WHERE id = b.keyword_id LIMIT 1) " + |
michael@0 | 463 | ")"); |
michael@0 | 464 | cleanupStatements.push(fixInvalidKeywords); |
michael@0 | 465 | |
michael@0 | 466 | // D.6 fix wrong item types |
michael@0 | 467 | // Folders and separators should not have an fk. |
michael@0 | 468 | // If they have a valid fk convert them to bookmarks. Later in D.9 we |
michael@0 | 469 | // will move eventual children to unsorted bookmarks. |
michael@0 | 470 | let fixBookmarksAsFolders = DBConn.createAsyncStatement( |
michael@0 | 471 | "UPDATE moz_bookmarks SET type = :bookmark_type WHERE id NOT IN ( " + |
michael@0 | 472 | "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots |
michael@0 | 473 | ") AND id IN ( " + |
michael@0 | 474 | "SELECT id FROM moz_bookmarks b " + |
michael@0 | 475 | "WHERE type IN (:folder_type, :separator_type) " + |
michael@0 | 476 | "AND fk NOTNULL " + |
michael@0 | 477 | ")"); |
michael@0 | 478 | fixBookmarksAsFolders.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; |
michael@0 | 479 | fixBookmarksAsFolders.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER; |
michael@0 | 480 | fixBookmarksAsFolders.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR; |
michael@0 | 481 | cleanupStatements.push(fixBookmarksAsFolders); |
michael@0 | 482 | |
michael@0 | 483 | // D.7 fix wrong item types |
michael@0 | 484 | // Bookmarks should have an fk, if they don't have any, convert them to |
michael@0 | 485 | // folders. |
michael@0 | 486 | let fixFoldersAsBookmarks = DBConn.createAsyncStatement( |
michael@0 | 487 | "UPDATE moz_bookmarks SET type = :folder_type WHERE id NOT IN ( " + |
michael@0 | 488 | "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots |
michael@0 | 489 | ") AND id IN ( " + |
michael@0 | 490 | "SELECT id FROM moz_bookmarks b " + |
michael@0 | 491 | "WHERE type = :bookmark_type " + |
michael@0 | 492 | "AND fk IS NULL " + |
michael@0 | 493 | ")"); |
michael@0 | 494 | fixFoldersAsBookmarks.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; |
michael@0 | 495 | fixFoldersAsBookmarks.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER; |
michael@0 | 496 | cleanupStatements.push(fixFoldersAsBookmarks); |
michael@0 | 497 | |
michael@0 | 498 | // D.9 fix wrong parents |
michael@0 | 499 | // Items cannot have separators or other bookmarks |
michael@0 | 500 | // as parent, if they have bad parent move them to unsorted bookmarks. |
michael@0 | 501 | let fixInvalidParents = DBConn.createAsyncStatement( |
michael@0 | 502 | "UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN ( " + |
michael@0 | 503 | "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots |
michael@0 | 504 | ") AND id IN ( " + |
michael@0 | 505 | "SELECT id FROM moz_bookmarks b " + |
michael@0 | 506 | "WHERE EXISTS " + |
michael@0 | 507 | "(SELECT id FROM moz_bookmarks WHERE id = b.parent " + |
michael@0 | 508 | "AND type IN (:bookmark_type, :separator_type) " + |
michael@0 | 509 | "LIMIT 1) " + |
michael@0 | 510 | ")"); |
michael@0 | 511 | fixInvalidParents.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId; |
michael@0 | 512 | fixInvalidParents.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; |
michael@0 | 513 | fixInvalidParents.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR; |
michael@0 | 514 | cleanupStatements.push(fixInvalidParents); |
michael@0 | 515 | |
michael@0 | 516 | // D.10 recalculate positions |
michael@0 | 517 | // This requires multiple related statements. |
michael@0 | 518 | // We can detect a folder with bad position values comparing the sum of |
michael@0 | 519 | // all distinct position values (+1 since position is 0-based) with the |
michael@0 | 520 | // triangular numbers obtained by the number of children (n). |
michael@0 | 521 | // SUM(DISTINCT position + 1) == (n * (n + 1) / 2). |
michael@0 | 522 | cleanupStatements.push(DBConn.createAsyncStatement( |
michael@0 | 523 | "CREATE TEMP TABLE IF NOT EXISTS moz_bm_reindex_temp ( " + |
michael@0 | 524 | " id INTEGER PRIMARY_KEY " + |
michael@0 | 525 | ", parent INTEGER " + |
michael@0 | 526 | ", position INTEGER " + |
michael@0 | 527 | ") " |
michael@0 | 528 | )); |
michael@0 | 529 | cleanupStatements.push(DBConn.createAsyncStatement( |
michael@0 | 530 | "INSERT INTO moz_bm_reindex_temp " + |
michael@0 | 531 | "SELECT id, parent, 0 " + |
michael@0 | 532 | "FROM moz_bookmarks b " + |
michael@0 | 533 | "WHERE parent IN ( " + |
michael@0 | 534 | "SELECT parent " + |
michael@0 | 535 | "FROM moz_bookmarks " + |
michael@0 | 536 | "GROUP BY parent " + |
michael@0 | 537 | "HAVING (SUM(DISTINCT position + 1) - (count(*) * (count(*) + 1) / 2)) <> 0 " + |
michael@0 | 538 | ") " + |
michael@0 | 539 | "ORDER BY parent ASC, position ASC, ROWID ASC " |
michael@0 | 540 | )); |
michael@0 | 541 | cleanupStatements.push(DBConn.createAsyncStatement( |
michael@0 | 542 | "CREATE INDEX IF NOT EXISTS moz_bm_reindex_temp_index " + |
michael@0 | 543 | "ON moz_bm_reindex_temp(parent)" |
michael@0 | 544 | )); |
michael@0 | 545 | cleanupStatements.push(DBConn.createAsyncStatement( |
michael@0 | 546 | "UPDATE moz_bm_reindex_temp SET position = ( " + |
michael@0 | 547 | "ROWID - (SELECT MIN(t.ROWID) FROM moz_bm_reindex_temp t " + |
michael@0 | 548 | "WHERE t.parent = moz_bm_reindex_temp.parent) " + |
michael@0 | 549 | ") " |
michael@0 | 550 | )); |
michael@0 | 551 | cleanupStatements.push(DBConn.createAsyncStatement( |
michael@0 | 552 | "CREATE TEMP TRIGGER IF NOT EXISTS moz_bm_reindex_temp_trigger " + |
michael@0 | 553 | "BEFORE DELETE ON moz_bm_reindex_temp " + |
michael@0 | 554 | "FOR EACH ROW " + |
michael@0 | 555 | "BEGIN " + |
michael@0 | 556 | "UPDATE moz_bookmarks SET position = OLD.position WHERE id = OLD.id; " + |
michael@0 | 557 | "END " |
michael@0 | 558 | )); |
michael@0 | 559 | cleanupStatements.push(DBConn.createAsyncStatement( |
michael@0 | 560 | "DELETE FROM moz_bm_reindex_temp " |
michael@0 | 561 | )); |
michael@0 | 562 | cleanupStatements.push(DBConn.createAsyncStatement( |
michael@0 | 563 | "DROP INDEX moz_bm_reindex_temp_index " |
michael@0 | 564 | )); |
michael@0 | 565 | cleanupStatements.push(DBConn.createAsyncStatement( |
michael@0 | 566 | "DROP TRIGGER moz_bm_reindex_temp_trigger " |
michael@0 | 567 | )); |
michael@0 | 568 | cleanupStatements.push(DBConn.createAsyncStatement( |
michael@0 | 569 | "DROP TABLE moz_bm_reindex_temp " |
michael@0 | 570 | )); |
michael@0 | 571 | |
michael@0 | 572 | // D.12 Fix empty-named tags. |
michael@0 | 573 | // Tags were allowed to have empty names due to a UI bug. Fix them |
michael@0 | 574 | // replacing their title with "(notitle)". |
michael@0 | 575 | let fixEmptyNamedTags = DBConn.createAsyncStatement( |
michael@0 | 576 | "UPDATE moz_bookmarks SET title = :empty_title " + |
michael@0 | 577 | "WHERE length(title) = 0 AND type = :folder_type " + |
michael@0 | 578 | "AND parent = :tags_folder" |
michael@0 | 579 | ); |
michael@0 | 580 | fixEmptyNamedTags.params["empty_title"] = "(notitle)"; |
michael@0 | 581 | fixEmptyNamedTags.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER; |
michael@0 | 582 | fixEmptyNamedTags.params["tags_folder"] = PlacesUtils.tagsFolderId; |
michael@0 | 583 | cleanupStatements.push(fixEmptyNamedTags); |
michael@0 | 584 | |
michael@0 | 585 | // MOZ_FAVICONS |
michael@0 | 586 | // E.1 remove orphan icons |
michael@0 | 587 | let deleteOrphanIcons = DBConn.createAsyncStatement( |
michael@0 | 588 | "DELETE FROM moz_favicons WHERE id IN (" + |
michael@0 | 589 | "SELECT id FROM moz_favicons f " + |
michael@0 | 590 | "WHERE NOT EXISTS " + |
michael@0 | 591 | "(SELECT id FROM moz_places WHERE favicon_id = f.id LIMIT 1) " + |
michael@0 | 592 | ")"); |
michael@0 | 593 | cleanupStatements.push(deleteOrphanIcons); |
michael@0 | 594 | |
michael@0 | 595 | // MOZ_HISTORYVISITS |
michael@0 | 596 | // F.1 remove orphan visits |
michael@0 | 597 | let deleteOrphanVisits = DBConn.createAsyncStatement( |
michael@0 | 598 | "DELETE FROM moz_historyvisits WHERE id IN (" + |
michael@0 | 599 | "SELECT id FROM moz_historyvisits v " + |
michael@0 | 600 | "WHERE NOT EXISTS " + |
michael@0 | 601 | "(SELECT id FROM moz_places WHERE id = v.place_id LIMIT 1) " + |
michael@0 | 602 | ")"); |
michael@0 | 603 | cleanupStatements.push(deleteOrphanVisits); |
michael@0 | 604 | |
michael@0 | 605 | // MOZ_INPUTHISTORY |
michael@0 | 606 | // G.1 remove orphan input history |
michael@0 | 607 | let deleteOrphanInputHistory = DBConn.createAsyncStatement( |
michael@0 | 608 | "DELETE FROM moz_inputhistory WHERE place_id IN (" + |
michael@0 | 609 | "SELECT place_id FROM moz_inputhistory i " + |
michael@0 | 610 | "WHERE NOT EXISTS " + |
michael@0 | 611 | "(SELECT id FROM moz_places WHERE id = i.place_id LIMIT 1) " + |
michael@0 | 612 | ")"); |
michael@0 | 613 | cleanupStatements.push(deleteOrphanInputHistory); |
michael@0 | 614 | |
michael@0 | 615 | // MOZ_ITEMS_ANNOS |
michael@0 | 616 | // H.1 remove item annos with an invalid attribute |
michael@0 | 617 | let deleteInvalidAttributeItemsAnnos = DBConn.createAsyncStatement( |
michael@0 | 618 | "DELETE FROM moz_items_annos WHERE id IN ( " + |
michael@0 | 619 | "SELECT id FROM moz_items_annos t " + |
michael@0 | 620 | "WHERE NOT EXISTS " + |
michael@0 | 621 | "(SELECT id FROM moz_anno_attributes " + |
michael@0 | 622 | "WHERE id = t.anno_attribute_id LIMIT 1) " + |
michael@0 | 623 | ")"); |
michael@0 | 624 | cleanupStatements.push(deleteInvalidAttributeItemsAnnos); |
michael@0 | 625 | |
michael@0 | 626 | // H.2 remove orphan item annos |
michael@0 | 627 | let deleteOrphanItemsAnnos = DBConn.createAsyncStatement( |
michael@0 | 628 | "DELETE FROM moz_items_annos WHERE id IN ( " + |
michael@0 | 629 | "SELECT id FROM moz_items_annos t " + |
michael@0 | 630 | "WHERE NOT EXISTS " + |
michael@0 | 631 | "(SELECT id FROM moz_bookmarks WHERE id = t.item_id LIMIT 1) " + |
michael@0 | 632 | ")"); |
michael@0 | 633 | cleanupStatements.push(deleteOrphanItemsAnnos); |
michael@0 | 634 | |
michael@0 | 635 | // MOZ_KEYWORDS |
michael@0 | 636 | // I.1 remove unused keywords |
michael@0 | 637 | let deleteUnusedKeywords = DBConn.createAsyncStatement( |
michael@0 | 638 | "DELETE FROM moz_keywords WHERE id IN ( " + |
michael@0 | 639 | "SELECT id FROM moz_keywords k " + |
michael@0 | 640 | "WHERE NOT EXISTS " + |
michael@0 | 641 | "(SELECT id FROM moz_bookmarks WHERE keyword_id = k.id LIMIT 1) " + |
michael@0 | 642 | ")"); |
michael@0 | 643 | cleanupStatements.push(deleteUnusedKeywords); |
michael@0 | 644 | |
michael@0 | 645 | // MOZ_PLACES |
michael@0 | 646 | // L.1 fix wrong favicon ids |
michael@0 | 647 | let fixInvalidFaviconIds = DBConn.createAsyncStatement( |
michael@0 | 648 | "UPDATE moz_places SET favicon_id = NULL WHERE id IN ( " + |
michael@0 | 649 | "SELECT id FROM moz_places h " + |
michael@0 | 650 | "WHERE favicon_id NOT NULL " + |
michael@0 | 651 | "AND NOT EXISTS " + |
michael@0 | 652 | "(SELECT id FROM moz_favicons WHERE id = h.favicon_id LIMIT 1) " + |
michael@0 | 653 | ")"); |
michael@0 | 654 | cleanupStatements.push(fixInvalidFaviconIds); |
michael@0 | 655 | |
michael@0 | 656 | // L.2 recalculate visit_count and last_visit_date |
michael@0 | 657 | let fixVisitStats = DBConn.createAsyncStatement( |
michael@0 | 658 | "UPDATE moz_places " + |
michael@0 | 659 | "SET visit_count = (SELECT count(*) FROM moz_historyvisits " + |
michael@0 | 660 | "WHERE place_id = moz_places.id AND visit_type NOT IN (0,4,7,8)), " + |
michael@0 | 661 | "last_visit_date = (SELECT MAX(visit_date) FROM moz_historyvisits " + |
michael@0 | 662 | "WHERE place_id = moz_places.id) " + |
michael@0 | 663 | "WHERE id IN ( " + |
michael@0 | 664 | "SELECT h.id FROM moz_places h " + |
michael@0 | 665 | "WHERE visit_count <> (SELECT count(*) FROM moz_historyvisits v " + |
michael@0 | 666 | "WHERE v.place_id = h.id AND visit_type NOT IN (0,4,7,8)) " + |
michael@0 | 667 | "OR last_visit_date <> (SELECT MAX(visit_date) FROM moz_historyvisits v " + |
michael@0 | 668 | "WHERE v.place_id = h.id) " + |
michael@0 | 669 | ")"); |
michael@0 | 670 | cleanupStatements.push(fixVisitStats); |
michael@0 | 671 | |
michael@0 | 672 | // L.3 recalculate hidden for redirects. |
michael@0 | 673 | let fixRedirectsHidden = DBConn.createAsyncStatement( |
michael@0 | 674 | "UPDATE moz_places " + |
michael@0 | 675 | "SET hidden = 1 " + |
michael@0 | 676 | "WHERE id IN ( " + |
michael@0 | 677 | "SELECT h.id FROM moz_places h " + |
michael@0 | 678 | "JOIN moz_historyvisits src ON src.place_id = h.id " + |
michael@0 | 679 | "JOIN moz_historyvisits dst ON dst.from_visit = src.id AND dst.visit_type IN (5,6) " + |
michael@0 | 680 | "LEFT JOIN moz_bookmarks on fk = h.id AND fk ISNULL " + |
michael@0 | 681 | "GROUP BY src.place_id HAVING count(*) = visit_count " + |
michael@0 | 682 | ")"); |
michael@0 | 683 | cleanupStatements.push(fixRedirectsHidden); |
michael@0 | 684 | |
michael@0 | 685 | // MAINTENANCE STATEMENTS SHOULD GO ABOVE THIS POINT! |
michael@0 | 686 | |
michael@0 | 687 | return cleanupStatements; |
michael@0 | 688 | }, |
michael@0 | 689 | |
michael@0 | 690 | /** |
michael@0 | 691 | * Tries to vacuum the database. |
michael@0 | 692 | * |
michael@0 | 693 | * @param [optional] aTasks |
michael@0 | 694 | * Tasks object to execute. |
michael@0 | 695 | */ |
michael@0 | 696 | vacuum: function PDBU_vacuum(aTasks) |
michael@0 | 697 | { |
michael@0 | 698 | let tasks = new Tasks(aTasks); |
michael@0 | 699 | tasks.log("> Vacuum"); |
michael@0 | 700 | |
michael@0 | 701 | let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); |
michael@0 | 702 | DBFile.append("places.sqlite"); |
michael@0 | 703 | tasks.log("Initial database size is " + |
michael@0 | 704 | parseInt(DBFile.fileSize / 1024) + " KiB"); |
michael@0 | 705 | |
michael@0 | 706 | let stmt = DBConn.createAsyncStatement("VACUUM"); |
michael@0 | 707 | stmt.executeAsync({ |
michael@0 | 708 | handleError: PlacesDBUtils._handleError, |
michael@0 | 709 | handleResult: function () {}, |
michael@0 | 710 | |
michael@0 | 711 | handleCompletion: function (aReason) |
michael@0 | 712 | { |
michael@0 | 713 | if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { |
michael@0 | 714 | tasks.log("+ The database has been vacuumed"); |
michael@0 | 715 | let vacuumedDBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); |
michael@0 | 716 | vacuumedDBFile.append("places.sqlite"); |
michael@0 | 717 | tasks.log("Final database size is " + |
michael@0 | 718 | parseInt(vacuumedDBFile.fileSize / 1024) + " KiB"); |
michael@0 | 719 | } |
michael@0 | 720 | else { |
michael@0 | 721 | tasks.log("- Unable to vacuum database"); |
michael@0 | 722 | tasks.clear(); |
michael@0 | 723 | } |
michael@0 | 724 | |
michael@0 | 725 | PlacesDBUtils._executeTasks(tasks); |
michael@0 | 726 | } |
michael@0 | 727 | }); |
michael@0 | 728 | stmt.finalize(); |
michael@0 | 729 | }, |
michael@0 | 730 | |
michael@0 | 731 | /** |
michael@0 | 732 | * Forces a full expiration on the database. |
michael@0 | 733 | * |
michael@0 | 734 | * @param [optional] aTasks |
michael@0 | 735 | * Tasks object to execute. |
michael@0 | 736 | */ |
michael@0 | 737 | expire: function PDBU_expire(aTasks) |
michael@0 | 738 | { |
michael@0 | 739 | let tasks = new Tasks(aTasks); |
michael@0 | 740 | tasks.log("> Orphans expiration"); |
michael@0 | 741 | |
michael@0 | 742 | let expiration = Cc["@mozilla.org/places/expiration;1"]. |
michael@0 | 743 | getService(Ci.nsIObserver); |
michael@0 | 744 | |
michael@0 | 745 | Services.obs.addObserver(function (aSubject, aTopic, aData) { |
michael@0 | 746 | Services.obs.removeObserver(arguments.callee, aTopic); |
michael@0 | 747 | tasks.log("+ Database cleaned up"); |
michael@0 | 748 | PlacesDBUtils._executeTasks(tasks); |
michael@0 | 749 | }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false); |
michael@0 | 750 | |
michael@0 | 751 | // Force an orphans expiration step. |
michael@0 | 752 | expiration.observe(null, "places-debug-start-expiration", 0); |
michael@0 | 753 | }, |
michael@0 | 754 | |
michael@0 | 755 | /** |
michael@0 | 756 | * Collects statistical data on the database. |
michael@0 | 757 | * |
michael@0 | 758 | * @param [optional] aTasks |
michael@0 | 759 | * Tasks object to execute. |
michael@0 | 760 | */ |
michael@0 | 761 | stats: function PDBU_stats(aTasks) |
michael@0 | 762 | { |
michael@0 | 763 | let tasks = new Tasks(aTasks); |
michael@0 | 764 | tasks.log("> Statistics"); |
michael@0 | 765 | |
michael@0 | 766 | let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); |
michael@0 | 767 | DBFile.append("places.sqlite"); |
michael@0 | 768 | tasks.log("Database size is " + parseInt(DBFile.fileSize / 1024) + " KiB"); |
michael@0 | 769 | |
michael@0 | 770 | [ "user_version" |
michael@0 | 771 | , "page_size" |
michael@0 | 772 | , "cache_size" |
michael@0 | 773 | , "journal_mode" |
michael@0 | 774 | , "synchronous" |
michael@0 | 775 | ].forEach(function (aPragma) { |
michael@0 | 776 | let stmt = DBConn.createStatement("PRAGMA " + aPragma); |
michael@0 | 777 | stmt.executeStep(); |
michael@0 | 778 | tasks.log(aPragma + " is " + stmt.getString(0)); |
michael@0 | 779 | stmt.finalize(); |
michael@0 | 780 | }); |
michael@0 | 781 | |
michael@0 | 782 | // Get maximum number of unique URIs. |
michael@0 | 783 | try { |
michael@0 | 784 | let limitURIs = Services.prefs.getIntPref( |
michael@0 | 785 | "places.history.expiration.transient_current_max_pages"); |
michael@0 | 786 | tasks.log("History can store a maximum of " + limitURIs + " unique pages"); |
michael@0 | 787 | } catch(ex) {} |
michael@0 | 788 | |
michael@0 | 789 | let stmt = DBConn.createStatement( |
michael@0 | 790 | "SELECT name FROM sqlite_master WHERE type = :type"); |
michael@0 | 791 | stmt.params.type = "table"; |
michael@0 | 792 | while (stmt.executeStep()) { |
michael@0 | 793 | let tableName = stmt.getString(0); |
michael@0 | 794 | let countStmt = DBConn.createStatement( |
michael@0 | 795 | "SELECT count(*) FROM " + tableName); |
michael@0 | 796 | countStmt.executeStep(); |
michael@0 | 797 | tasks.log("Table " + tableName + " has " + countStmt.getInt32(0) + " records"); |
michael@0 | 798 | countStmt.finalize(); |
michael@0 | 799 | } |
michael@0 | 800 | stmt.reset(); |
michael@0 | 801 | |
michael@0 | 802 | stmt.params.type = "index"; |
michael@0 | 803 | while (stmt.executeStep()) { |
michael@0 | 804 | tasks.log("Index " + stmt.getString(0)); |
michael@0 | 805 | } |
michael@0 | 806 | stmt.reset(); |
michael@0 | 807 | |
michael@0 | 808 | stmt.params.type = "trigger"; |
michael@0 | 809 | while (stmt.executeStep()) { |
michael@0 | 810 | tasks.log("Trigger " + stmt.getString(0)); |
michael@0 | 811 | } |
michael@0 | 812 | stmt.finalize(); |
michael@0 | 813 | |
michael@0 | 814 | PlacesDBUtils._executeTasks(tasks); |
michael@0 | 815 | }, |
michael@0 | 816 | |
michael@0 | 817 | /** |
michael@0 | 818 | * Collects telemetry data. |
michael@0 | 819 | * |
michael@0 | 820 | * There are essentially two modes of collection and the mode is |
michael@0 | 821 | * determined by the presence of aHealthReportCallback. If |
michael@0 | 822 | * aHealthReportCallback is not defined (the default) then we are in |
michael@0 | 823 | * "Telemetry" mode. Results will be reported to Telemetry. If we are |
michael@0 | 824 | * in "Health Report" mode only the probes with a true healthreport |
michael@0 | 825 | * flag will be collected and the results will be reported to the |
michael@0 | 826 | * aHealthReportCallback. |
michael@0 | 827 | * |
michael@0 | 828 | * @param [optional] aTasks |
michael@0 | 829 | * Tasks object to execute. |
michael@0 | 830 | * @param [optional] aHealthReportCallback |
michael@0 | 831 | * Function to receive data relevant for Firefox Health Report. |
michael@0 | 832 | */ |
michael@0 | 833 | telemetry: function PDBU_telemetry(aTasks, aHealthReportCallback=null) |
michael@0 | 834 | { |
michael@0 | 835 | let tasks = new Tasks(aTasks); |
michael@0 | 836 | |
michael@0 | 837 | let isTelemetry = !aHealthReportCallback; |
michael@0 | 838 | |
michael@0 | 839 | // This will be populated with one integer property for each probe result, |
michael@0 | 840 | // using the histogram name as key. |
michael@0 | 841 | let probeValues = {}; |
michael@0 | 842 | |
michael@0 | 843 | // The following array contains an ordered list of entries that are |
michael@0 | 844 | // processed to collect telemetry data. Each entry has these properties: |
michael@0 | 845 | // |
michael@0 | 846 | // histogram: Name of the telemetry histogram to update. |
michael@0 | 847 | // query: This is optional. If present, contains a database command |
michael@0 | 848 | // that will be executed asynchronously, and whose result will |
michael@0 | 849 | // be added to the telemetry histogram. |
michael@0 | 850 | // callback: This is optional. If present, contains a function that must |
michael@0 | 851 | // return the value that will be added to the telemetry |
michael@0 | 852 | // histogram. If a query is also present, its result is passed |
michael@0 | 853 | // as the first argument of the function. If the function |
michael@0 | 854 | // raises an exception, no data is added to the histogram. |
michael@0 | 855 | // healthreport: Boolean indicating whether this probe is relevant |
michael@0 | 856 | // to Firefox Health Report. |
michael@0 | 857 | // |
michael@0 | 858 | // Since all queries are executed in order by the database backend, the |
michael@0 | 859 | // callbacks can also use the result of previous queries stored in the |
michael@0 | 860 | // probeValues object. |
michael@0 | 861 | let probes = [ |
michael@0 | 862 | { histogram: "PLACES_PAGES_COUNT", |
michael@0 | 863 | healthreport: true, |
michael@0 | 864 | query: "SELECT count(*) FROM moz_places" }, |
michael@0 | 865 | |
michael@0 | 866 | { histogram: "PLACES_BOOKMARKS_COUNT", |
michael@0 | 867 | healthreport: true, |
michael@0 | 868 | query: "SELECT count(*) FROM moz_bookmarks b " |
michael@0 | 869 | + "JOIN moz_bookmarks t ON t.id = b.parent " |
michael@0 | 870 | + "AND t.parent <> :tags_folder " |
michael@0 | 871 | + "WHERE b.type = :type_bookmark " }, |
michael@0 | 872 | |
michael@0 | 873 | { histogram: "PLACES_TAGS_COUNT", |
michael@0 | 874 | query: "SELECT count(*) FROM moz_bookmarks " |
michael@0 | 875 | + "WHERE parent = :tags_folder " }, |
michael@0 | 876 | |
michael@0 | 877 | { histogram: "PLACES_FOLDERS_COUNT", |
michael@0 | 878 | query: "SELECT count(*) FROM moz_bookmarks " |
michael@0 | 879 | + "WHERE TYPE = :type_folder " |
michael@0 | 880 | + "AND parent NOT IN (0, :places_root, :tags_folder) " }, |
michael@0 | 881 | |
michael@0 | 882 | { histogram: "PLACES_KEYWORDS_COUNT", |
michael@0 | 883 | query: "SELECT count(*) FROM moz_keywords " }, |
michael@0 | 884 | |
michael@0 | 885 | { histogram: "PLACES_SORTED_BOOKMARKS_PERC", |
michael@0 | 886 | query: "SELECT IFNULL(ROUND(( " |
michael@0 | 887 | + "SELECT count(*) FROM moz_bookmarks b " |
michael@0 | 888 | + "JOIN moz_bookmarks t ON t.id = b.parent " |
michael@0 | 889 | + "AND t.parent <> :tags_folder AND t.parent > :places_root " |
michael@0 | 890 | + "WHERE b.type = :type_bookmark " |
michael@0 | 891 | + ") * 100 / ( " |
michael@0 | 892 | + "SELECT count(*) FROM moz_bookmarks b " |
michael@0 | 893 | + "JOIN moz_bookmarks t ON t.id = b.parent " |
michael@0 | 894 | + "AND t.parent <> :tags_folder " |
michael@0 | 895 | + "WHERE b.type = :type_bookmark " |
michael@0 | 896 | + ")), 0) " }, |
michael@0 | 897 | |
michael@0 | 898 | { histogram: "PLACES_TAGGED_BOOKMARKS_PERC", |
michael@0 | 899 | query: "SELECT IFNULL(ROUND(( " |
michael@0 | 900 | + "SELECT count(*) FROM moz_bookmarks b " |
michael@0 | 901 | + "JOIN moz_bookmarks t ON t.id = b.parent " |
michael@0 | 902 | + "AND t.parent = :tags_folder " |
michael@0 | 903 | + ") * 100 / ( " |
michael@0 | 904 | + "SELECT count(*) FROM moz_bookmarks b " |
michael@0 | 905 | + "JOIN moz_bookmarks t ON t.id = b.parent " |
michael@0 | 906 | + "AND t.parent <> :tags_folder " |
michael@0 | 907 | + "WHERE b.type = :type_bookmark " |
michael@0 | 908 | + ")), 0) " }, |
michael@0 | 909 | |
michael@0 | 910 | { histogram: "PLACES_DATABASE_FILESIZE_MB", |
michael@0 | 911 | callback: function () { |
michael@0 | 912 | let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); |
michael@0 | 913 | DBFile.append("places.sqlite"); |
michael@0 | 914 | return parseInt(DBFile.fileSize / BYTES_PER_MEBIBYTE); |
michael@0 | 915 | } |
michael@0 | 916 | }, |
michael@0 | 917 | |
michael@0 | 918 | { histogram: "PLACES_DATABASE_JOURNALSIZE_MB", |
michael@0 | 919 | callback: function () { |
michael@0 | 920 | let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); |
michael@0 | 921 | DBFile.append("places.sqlite-wal"); |
michael@0 | 922 | return parseInt(DBFile.fileSize / BYTES_PER_MEBIBYTE); |
michael@0 | 923 | } |
michael@0 | 924 | }, |
michael@0 | 925 | |
michael@0 | 926 | { histogram: "PLACES_DATABASE_PAGESIZE_B", |
michael@0 | 927 | query: "PRAGMA page_size /* PlacesDBUtils.jsm PAGESIZE_B */" }, |
michael@0 | 928 | |
michael@0 | 929 | { histogram: "PLACES_DATABASE_SIZE_PER_PAGE_B", |
michael@0 | 930 | query: "PRAGMA page_count", |
michael@0 | 931 | callback: function (aDbPageCount) { |
michael@0 | 932 | // Note that the database file size would not be meaningful for this |
michael@0 | 933 | // calculation, because the file grows in fixed-size chunks. |
michael@0 | 934 | let dbPageSize = probeValues.PLACES_DATABASE_PAGESIZE_B; |
michael@0 | 935 | let placesPageCount = probeValues.PLACES_PAGES_COUNT; |
michael@0 | 936 | return Math.round((dbPageSize * aDbPageCount) / placesPageCount); |
michael@0 | 937 | } |
michael@0 | 938 | }, |
michael@0 | 939 | |
michael@0 | 940 | { histogram: "PLACES_ANNOS_BOOKMARKS_COUNT", |
michael@0 | 941 | query: "SELECT count(*) FROM moz_items_annos" }, |
michael@0 | 942 | |
michael@0 | 943 | // LENGTH is not a perfect measure, since it returns the number of bytes |
michael@0 | 944 | // only for BLOBs, the number of chars for anything else. Though it's |
michael@0 | 945 | // the best approximation we have. |
michael@0 | 946 | { histogram: "PLACES_ANNOS_BOOKMARKS_SIZE_KB", |
michael@0 | 947 | query: "SELECT SUM(LENGTH(content))/1024 FROM moz_items_annos" }, |
michael@0 | 948 | |
michael@0 | 949 | { histogram: "PLACES_ANNOS_PAGES_COUNT", |
michael@0 | 950 | query: "SELECT count(*) FROM moz_annos" }, |
michael@0 | 951 | |
michael@0 | 952 | { histogram: "PLACES_ANNOS_PAGES_SIZE_KB", |
michael@0 | 953 | query: "SELECT SUM(LENGTH(content))/1024 FROM moz_annos" }, |
michael@0 | 954 | ]; |
michael@0 | 955 | |
michael@0 | 956 | let params = { |
michael@0 | 957 | tags_folder: PlacesUtils.tagsFolderId, |
michael@0 | 958 | type_folder: PlacesUtils.bookmarks.TYPE_FOLDER, |
michael@0 | 959 | type_bookmark: PlacesUtils.bookmarks.TYPE_BOOKMARK, |
michael@0 | 960 | places_root: PlacesUtils.placesRootId |
michael@0 | 961 | }; |
michael@0 | 962 | |
michael@0 | 963 | let outstandingProbes = 0; |
michael@0 | 964 | |
michael@0 | 965 | function reportResult(aProbe, aValue) { |
michael@0 | 966 | outstandingProbes--; |
michael@0 | 967 | |
michael@0 | 968 | let value = aValue; |
michael@0 | 969 | try { |
michael@0 | 970 | if ("callback" in aProbe) { |
michael@0 | 971 | value = aProbe.callback(value); |
michael@0 | 972 | } |
michael@0 | 973 | probeValues[aProbe.histogram] = value; |
michael@0 | 974 | Services.telemetry.getHistogramById(aProbe.histogram).add(value); |
michael@0 | 975 | } catch (ex) { |
michael@0 | 976 | Components.utils.reportError("Error adding value " + value + |
michael@0 | 977 | " to histogram " + aProbe.histogram + |
michael@0 | 978 | ": " + ex); |
michael@0 | 979 | } |
michael@0 | 980 | |
michael@0 | 981 | if (!outstandingProbes && aHealthReportCallback) { |
michael@0 | 982 | try { |
michael@0 | 983 | aHealthReportCallback(probeValues); |
michael@0 | 984 | } catch (ex) { |
michael@0 | 985 | Components.utils.reportError(ex); |
michael@0 | 986 | } |
michael@0 | 987 | } |
michael@0 | 988 | } |
michael@0 | 989 | |
michael@0 | 990 | for (let i = 0; i < probes.length; i++) { |
michael@0 | 991 | let probe = probes[i]; |
michael@0 | 992 | |
michael@0 | 993 | if (!isTelemetry && !probe.healthreport) { |
michael@0 | 994 | continue; |
michael@0 | 995 | } |
michael@0 | 996 | |
michael@0 | 997 | outstandingProbes++; |
michael@0 | 998 | |
michael@0 | 999 | if (!("query" in probe)) { |
michael@0 | 1000 | reportResult(probe); |
michael@0 | 1001 | continue; |
michael@0 | 1002 | } |
michael@0 | 1003 | |
michael@0 | 1004 | let stmt = DBConn.createAsyncStatement(probe.query); |
michael@0 | 1005 | for (param in params) { |
michael@0 | 1006 | if (probe.query.indexOf(":" + param) > 0) { |
michael@0 | 1007 | stmt.params[param] = params[param]; |
michael@0 | 1008 | } |
michael@0 | 1009 | } |
michael@0 | 1010 | |
michael@0 | 1011 | try { |
michael@0 | 1012 | stmt.executeAsync({ |
michael@0 | 1013 | handleError: PlacesDBUtils._handleError, |
michael@0 | 1014 | handleResult: function (aResultSet) { |
michael@0 | 1015 | let row = aResultSet.getNextRow(); |
michael@0 | 1016 | reportResult(probe, row.getResultByIndex(0)); |
michael@0 | 1017 | }, |
michael@0 | 1018 | handleCompletion: function () {} |
michael@0 | 1019 | }); |
michael@0 | 1020 | } finally{ |
michael@0 | 1021 | stmt.finalize(); |
michael@0 | 1022 | } |
michael@0 | 1023 | } |
michael@0 | 1024 | |
michael@0 | 1025 | PlacesDBUtils._executeTasks(tasks); |
michael@0 | 1026 | }, |
michael@0 | 1027 | |
michael@0 | 1028 | /** |
michael@0 | 1029 | * Runs a list of tasks, notifying log messages to the callback. |
michael@0 | 1030 | * |
michael@0 | 1031 | * @param aTasks |
michael@0 | 1032 | * Array of tasks to be executed, in form of pointers to methods in |
michael@0 | 1033 | * this module. |
michael@0 | 1034 | * @param [optional] aCallback |
michael@0 | 1035 | * Callback to be invoked when done. It will receive an array of |
michael@0 | 1036 | * log messages. |
michael@0 | 1037 | */ |
michael@0 | 1038 | runTasks: function PDBU_runTasks(aTasks, aCallback) { |
michael@0 | 1039 | let tasks = new Tasks(aTasks); |
michael@0 | 1040 | tasks.callback = aCallback; |
michael@0 | 1041 | PlacesDBUtils._executeTasks(tasks); |
michael@0 | 1042 | } |
michael@0 | 1043 | }; |
michael@0 | 1044 | |
michael@0 | 1045 | /** |
michael@0 | 1046 | * LIFO tasks stack. |
michael@0 | 1047 | * |
michael@0 | 1048 | * @param [optional] aTasks |
michael@0 | 1049 | * Array of tasks or another Tasks object to clone. |
michael@0 | 1050 | */ |
michael@0 | 1051 | function Tasks(aTasks) |
michael@0 | 1052 | { |
michael@0 | 1053 | if (aTasks) { |
michael@0 | 1054 | if (Array.isArray(aTasks)) { |
michael@0 | 1055 | this._list = aTasks.slice(0, aTasks.length); |
michael@0 | 1056 | } |
michael@0 | 1057 | // This supports passing in a Tasks-like object, with a "list" property, |
michael@0 | 1058 | // for compatibility reasons. |
michael@0 | 1059 | else if (typeof(aTasks) == "object" && |
michael@0 | 1060 | (Tasks instanceof Tasks || "list" in aTasks)) { |
michael@0 | 1061 | this._list = aTasks.list; |
michael@0 | 1062 | this._log = aTasks.messages; |
michael@0 | 1063 | this.callback = aTasks.callback; |
michael@0 | 1064 | this.scope = aTasks.scope; |
michael@0 | 1065 | this._telemetryStart = aTasks._telemetryStart; |
michael@0 | 1066 | } |
michael@0 | 1067 | } |
michael@0 | 1068 | } |
michael@0 | 1069 | |
michael@0 | 1070 | Tasks.prototype = { |
michael@0 | 1071 | _list: [], |
michael@0 | 1072 | _log: [], |
michael@0 | 1073 | callback: null, |
michael@0 | 1074 | scope: null, |
michael@0 | 1075 | _telemetryStart: 0, |
michael@0 | 1076 | |
michael@0 | 1077 | /** |
michael@0 | 1078 | * Adds a task to the top of the list. |
michael@0 | 1079 | * |
michael@0 | 1080 | * @param aNewElt |
michael@0 | 1081 | * Task to be added. |
michael@0 | 1082 | */ |
michael@0 | 1083 | push: function T_push(aNewElt) |
michael@0 | 1084 | { |
michael@0 | 1085 | this._list.unshift(aNewElt); |
michael@0 | 1086 | }, |
michael@0 | 1087 | |
michael@0 | 1088 | /** |
michael@0 | 1089 | * Returns and consumes next task. |
michael@0 | 1090 | * |
michael@0 | 1091 | * @return next task or undefined if no task is left. |
michael@0 | 1092 | */ |
michael@0 | 1093 | pop: function T_pop() this._list.shift(), |
michael@0 | 1094 | |
michael@0 | 1095 | /** |
michael@0 | 1096 | * Removes all tasks. |
michael@0 | 1097 | */ |
michael@0 | 1098 | clear: function T_clear() |
michael@0 | 1099 | { |
michael@0 | 1100 | this._list.length = 0; |
michael@0 | 1101 | }, |
michael@0 | 1102 | |
michael@0 | 1103 | /** |
michael@0 | 1104 | * Returns array of tasks ordered from the next to be run to the latest. |
michael@0 | 1105 | */ |
michael@0 | 1106 | get list() this._list.slice(0, this._list.length), |
michael@0 | 1107 | |
michael@0 | 1108 | /** |
michael@0 | 1109 | * Adds a message to the log. |
michael@0 | 1110 | * |
michael@0 | 1111 | * @param aMsg |
michael@0 | 1112 | * String message to be added. |
michael@0 | 1113 | */ |
michael@0 | 1114 | log: function T_log(aMsg) |
michael@0 | 1115 | { |
michael@0 | 1116 | this._log.push(aMsg); |
michael@0 | 1117 | }, |
michael@0 | 1118 | |
michael@0 | 1119 | /** |
michael@0 | 1120 | * Returns array of log messages ordered from oldest to newest. |
michael@0 | 1121 | */ |
michael@0 | 1122 | get messages() this._log.slice(0, this._log.length), |
michael@0 | 1123 | } |