1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/places/PlacesDBUtils.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1123 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * vim: sw=2 ts=2 sts=2 expandtab filetype=javascript 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cr = Components.results; 1.13 +const Cu = Components.utils; 1.14 + 1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.16 +Cu.import("resource://gre/modules/Services.jsm"); 1.17 +Cu.import("resource://gre/modules/PlacesUtils.jsm"); 1.18 + 1.19 +this.EXPORTED_SYMBOLS = [ "PlacesDBUtils" ]; 1.20 + 1.21 +//////////////////////////////////////////////////////////////////////////////// 1.22 +//// Constants 1.23 + 1.24 +const FINISHED_MAINTENANCE_TOPIC = "places-maintenance-finished"; 1.25 + 1.26 +const BYTES_PER_MEBIBYTE = 1048576; 1.27 + 1.28 +//////////////////////////////////////////////////////////////////////////////// 1.29 +//// Smart getters 1.30 + 1.31 +XPCOMUtils.defineLazyGetter(this, "DBConn", function() { 1.32 + return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; 1.33 +}); 1.34 + 1.35 +//////////////////////////////////////////////////////////////////////////////// 1.36 +//// PlacesDBUtils 1.37 + 1.38 +this.PlacesDBUtils = { 1.39 + /** 1.40 + * Executes a list of maintenance tasks. 1.41 + * Once finished it will pass a array log to the callback attached to tasks. 1.42 + * FINISHED_MAINTENANCE_TOPIC is notified through observer service on finish. 1.43 + * 1.44 + * @param aTasks 1.45 + * Tasks object to execute. 1.46 + */ 1.47 + _executeTasks: function PDBU__executeTasks(aTasks) 1.48 + { 1.49 + if (PlacesDBUtils._isShuttingDown) { 1.50 + aTasks.log("- We are shutting down. Will not schedule the tasks."); 1.51 + aTasks.clear(); 1.52 + } 1.53 + 1.54 + let task = aTasks.pop(); 1.55 + if (task) { 1.56 + task.call(PlacesDBUtils, aTasks); 1.57 + } 1.58 + else { 1.59 + // All tasks have been completed. 1.60 + // Telemetry the time it took for maintenance, if a start time exists. 1.61 + if (aTasks._telemetryStart) { 1.62 + Services.telemetry.getHistogramById("PLACES_IDLE_MAINTENANCE_TIME_MS") 1.63 + .add(Date.now() - aTasks._telemetryStart); 1.64 + aTasks._telemetryStart = 0; 1.65 + } 1.66 + 1.67 + if (aTasks.callback) { 1.68 + let scope = aTasks.scope || Cu.getGlobalForObject(aTasks.callback); 1.69 + aTasks.callback.call(scope, aTasks.messages); 1.70 + } 1.71 + 1.72 + // Notify observers that maintenance finished. 1.73 + Services.prefs.setIntPref("places.database.lastMaintenance", parseInt(Date.now() / 1000)); 1.74 + Services.obs.notifyObservers(null, FINISHED_MAINTENANCE_TOPIC, null); 1.75 + } 1.76 + }, 1.77 + 1.78 + _isShuttingDown : false, 1.79 + shutdown: function PDBU_shutdown() { 1.80 + PlacesDBUtils._isShuttingDown = true; 1.81 + }, 1.82 + 1.83 + /** 1.84 + * Executes integrity check and common maintenance tasks. 1.85 + * 1.86 + * @param [optional] aCallback 1.87 + * Callback to be invoked when done. The callback will get a array 1.88 + * of log messages. 1.89 + * @param [optional] aScope 1.90 + * Scope for the callback. 1.91 + */ 1.92 + maintenanceOnIdle: function PDBU_maintenanceOnIdle(aCallback, aScope) 1.93 + { 1.94 + let tasks = new Tasks([ 1.95 + this.checkIntegrity 1.96 + , this.checkCoherence 1.97 + , this._refreshUI 1.98 + ]); 1.99 + tasks._telemetryStart = Date.now(); 1.100 + tasks.callback = aCallback; 1.101 + tasks.scope = aScope; 1.102 + this._executeTasks(tasks); 1.103 + }, 1.104 + 1.105 + /** 1.106 + * Executes integrity check, common and advanced maintenance tasks (like 1.107 + * expiration and vacuum). Will also collect statistics on the database. 1.108 + * 1.109 + * @param [optional] aCallback 1.110 + * Callback to be invoked when done. The callback will get a array 1.111 + * of log messages. 1.112 + * @param [optional] aScope 1.113 + * Scope for the callback. 1.114 + */ 1.115 + checkAndFixDatabase: function PDBU_checkAndFixDatabase(aCallback, aScope) 1.116 + { 1.117 + let tasks = new Tasks([ 1.118 + this.checkIntegrity 1.119 + , this.checkCoherence 1.120 + , this.expire 1.121 + , this.vacuum 1.122 + , this.stats 1.123 + , this._refreshUI 1.124 + ]); 1.125 + tasks.callback = aCallback; 1.126 + tasks.scope = aScope; 1.127 + this._executeTasks(tasks); 1.128 + }, 1.129 + 1.130 + /** 1.131 + * Forces a full refresh of Places views. 1.132 + * 1.133 + * @param [optional] aTasks 1.134 + * Tasks object to execute. 1.135 + */ 1.136 + _refreshUI: function PDBU__refreshUI(aTasks) 1.137 + { 1.138 + let tasks = new Tasks(aTasks); 1.139 + 1.140 + // Send batch update notifications to update the UI. 1.141 + PlacesUtils.history.runInBatchMode({ 1.142 + runBatched: function (aUserData) {} 1.143 + }, null); 1.144 + PlacesDBUtils._executeTasks(tasks); 1.145 + }, 1.146 + 1.147 + _handleError: function PDBU__handleError(aError) 1.148 + { 1.149 + Cu.reportError("Async statement execution returned with '" + 1.150 + aError.result + "', '" + aError.message + "'"); 1.151 + }, 1.152 + 1.153 + /** 1.154 + * Tries to execute a REINDEX on the database. 1.155 + * 1.156 + * @param [optional] aTasks 1.157 + * Tasks object to execute. 1.158 + */ 1.159 + reindex: function PDBU_reindex(aTasks) 1.160 + { 1.161 + let tasks = new Tasks(aTasks); 1.162 + tasks.log("> Reindex"); 1.163 + 1.164 + let stmt = DBConn.createAsyncStatement("REINDEX"); 1.165 + stmt.executeAsync({ 1.166 + handleError: PlacesDBUtils._handleError, 1.167 + handleResult: function () {}, 1.168 + 1.169 + handleCompletion: function (aReason) 1.170 + { 1.171 + if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { 1.172 + tasks.log("+ The database has been reindexed"); 1.173 + } 1.174 + else { 1.175 + tasks.log("- Unable to reindex database"); 1.176 + } 1.177 + 1.178 + PlacesDBUtils._executeTasks(tasks); 1.179 + } 1.180 + }); 1.181 + stmt.finalize(); 1.182 + }, 1.183 + 1.184 + /** 1.185 + * Checks integrity but does not try to fix the database through a reindex. 1.186 + * 1.187 + * @param [optional] aTasks 1.188 + * Tasks object to execute. 1.189 + */ 1.190 + _checkIntegritySkipReindex: function PDBU__checkIntegritySkipReindex(aTasks) 1.191 + this.checkIntegrity(aTasks, true), 1.192 + 1.193 + /** 1.194 + * Checks integrity and tries to fix the database through a reindex. 1.195 + * 1.196 + * @param [optional] aTasks 1.197 + * Tasks object to execute. 1.198 + * @param [optional] aSkipdReindex 1.199 + * Whether to try to reindex database or not. 1.200 + */ 1.201 + checkIntegrity: function PDBU_checkIntegrity(aTasks, aSkipReindex) 1.202 + { 1.203 + let tasks = new Tasks(aTasks); 1.204 + tasks.log("> Integrity check"); 1.205 + 1.206 + // Run a integrity check, but stop at the first error. 1.207 + let stmt = DBConn.createAsyncStatement("PRAGMA integrity_check(1)"); 1.208 + stmt.executeAsync({ 1.209 + handleError: PlacesDBUtils._handleError, 1.210 + 1.211 + _corrupt: false, 1.212 + handleResult: function (aResultSet) 1.213 + { 1.214 + let row = aResultSet.getNextRow(); 1.215 + this._corrupt = row.getResultByIndex(0) != "ok"; 1.216 + }, 1.217 + 1.218 + handleCompletion: function (aReason) 1.219 + { 1.220 + if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { 1.221 + if (this._corrupt) { 1.222 + tasks.log("- The database is corrupt"); 1.223 + if (aSkipReindex) { 1.224 + tasks.log("- Unable to fix corruption, database will be replaced on next startup"); 1.225 + Services.prefs.setBoolPref("places.database.replaceOnStartup", true); 1.226 + tasks.clear(); 1.227 + } 1.228 + else { 1.229 + // Try to reindex, this often fixed simple indices corruption. 1.230 + // We insert from the top of the queue, they will run inverse. 1.231 + tasks.push(PlacesDBUtils._checkIntegritySkipReindex); 1.232 + tasks.push(PlacesDBUtils.reindex); 1.233 + } 1.234 + } 1.235 + else { 1.236 + tasks.log("+ The database is sane"); 1.237 + } 1.238 + } 1.239 + else { 1.240 + tasks.log("- Unable to check database status"); 1.241 + tasks.clear(); 1.242 + } 1.243 + 1.244 + PlacesDBUtils._executeTasks(tasks); 1.245 + } 1.246 + }); 1.247 + stmt.finalize(); 1.248 + }, 1.249 + 1.250 + /** 1.251 + * Checks data coherence and tries to fix most common errors. 1.252 + * 1.253 + * @param [optional] aTasks 1.254 + * Tasks object to execute. 1.255 + */ 1.256 + checkCoherence: function PDBU_checkCoherence(aTasks) 1.257 + { 1.258 + let tasks = new Tasks(aTasks); 1.259 + tasks.log("> Coherence check"); 1.260 + 1.261 + let stmts = PlacesDBUtils._getBoundCoherenceStatements(); 1.262 + DBConn.executeAsync(stmts, stmts.length, { 1.263 + handleError: PlacesDBUtils._handleError, 1.264 + handleResult: function () {}, 1.265 + 1.266 + handleCompletion: function (aReason) 1.267 + { 1.268 + if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { 1.269 + tasks.log("+ The database is coherent"); 1.270 + } 1.271 + else { 1.272 + tasks.log("- Unable to check database coherence"); 1.273 + tasks.clear(); 1.274 + } 1.275 + 1.276 + PlacesDBUtils._executeTasks(tasks); 1.277 + } 1.278 + }); 1.279 + stmts.forEach(function (aStmt) aStmt.finalize()); 1.280 + }, 1.281 + 1.282 + _getBoundCoherenceStatements: function PDBU__getBoundCoherenceStatements() 1.283 + { 1.284 + let cleanupStatements = []; 1.285 + 1.286 + // MOZ_ANNO_ATTRIBUTES 1.287 + // A.1 remove obsolete annotations from moz_annos. 1.288 + // The 'weave0' idiom exploits character ordering (0 follows /) to 1.289 + // efficiently select all annos with a 'weave/' prefix. 1.290 + let deleteObsoleteAnnos = DBConn.createAsyncStatement( 1.291 + "DELETE FROM moz_annos " + 1.292 + "WHERE anno_attribute_id IN ( " + 1.293 + " SELECT id FROM moz_anno_attributes " + 1.294 + " WHERE name BETWEEN 'weave/' AND 'weave0' " + 1.295 + ")"); 1.296 + cleanupStatements.push(deleteObsoleteAnnos); 1.297 + 1.298 + // A.2 remove obsolete annotations from moz_items_annos. 1.299 + let deleteObsoleteItemsAnnos = DBConn.createAsyncStatement( 1.300 + "DELETE FROM moz_items_annos " + 1.301 + "WHERE anno_attribute_id IN ( " + 1.302 + " SELECT id FROM moz_anno_attributes " + 1.303 + " WHERE name = 'sync/children' " + 1.304 + " OR name = 'placesInternal/GUID' " + 1.305 + " OR name BETWEEN 'weave/' AND 'weave0' " + 1.306 + ")"); 1.307 + cleanupStatements.push(deleteObsoleteItemsAnnos); 1.308 + 1.309 + // A.3 remove unused attributes. 1.310 + let deleteUnusedAnnoAttributes = DBConn.createAsyncStatement( 1.311 + "DELETE FROM moz_anno_attributes WHERE id IN ( " + 1.312 + "SELECT id FROM moz_anno_attributes n " + 1.313 + "WHERE NOT EXISTS " + 1.314 + "(SELECT id FROM moz_annos WHERE anno_attribute_id = n.id LIMIT 1) " + 1.315 + "AND NOT EXISTS " + 1.316 + "(SELECT id FROM moz_items_annos WHERE anno_attribute_id = n.id LIMIT 1) " + 1.317 + ")"); 1.318 + cleanupStatements.push(deleteUnusedAnnoAttributes); 1.319 + 1.320 + // MOZ_ANNOS 1.321 + // B.1 remove annos with an invalid attribute 1.322 + let deleteInvalidAttributeAnnos = DBConn.createAsyncStatement( 1.323 + "DELETE FROM moz_annos WHERE id IN ( " + 1.324 + "SELECT id FROM moz_annos a " + 1.325 + "WHERE NOT EXISTS " + 1.326 + "(SELECT id FROM moz_anno_attributes " + 1.327 + "WHERE id = a.anno_attribute_id LIMIT 1) " + 1.328 + ")"); 1.329 + cleanupStatements.push(deleteInvalidAttributeAnnos); 1.330 + 1.331 + // B.2 remove orphan annos 1.332 + let deleteOrphanAnnos = DBConn.createAsyncStatement( 1.333 + "DELETE FROM moz_annos WHERE id IN ( " + 1.334 + "SELECT id FROM moz_annos a " + 1.335 + "WHERE NOT EXISTS " + 1.336 + "(SELECT id FROM moz_places WHERE id = a.place_id LIMIT 1) " + 1.337 + ")"); 1.338 + cleanupStatements.push(deleteOrphanAnnos); 1.339 + 1.340 + // MOZ_BOOKMARKS_ROOTS 1.341 + // C.1 fix missing Places root 1.342 + // Bug 477739 shows a case where the root could be wrongly removed 1.343 + // due to an endianness issue. We try to fix broken roots here. 1.344 + let selectPlacesRoot = DBConn.createStatement( 1.345 + "SELECT id FROM moz_bookmarks WHERE id = :places_root"); 1.346 + selectPlacesRoot.params["places_root"] = PlacesUtils.placesRootId; 1.347 + if (!selectPlacesRoot.executeStep()) { 1.348 + // We are missing the root, try to recreate it. 1.349 + let createPlacesRoot = DBConn.createAsyncStatement( 1.350 + "INSERT INTO moz_bookmarks (id, type, fk, parent, position, title, " 1.351 + + "guid) " 1.352 + + "VALUES (:places_root, 2, NULL, 0, 0, :title, GENERATE_GUID())"); 1.353 + createPlacesRoot.params["places_root"] = PlacesUtils.placesRootId; 1.354 + createPlacesRoot.params["title"] = ""; 1.355 + cleanupStatements.push(createPlacesRoot); 1.356 + 1.357 + // Now ensure that other roots are children of Places root. 1.358 + let fixPlacesRootChildren = DBConn.createAsyncStatement( 1.359 + "UPDATE moz_bookmarks SET parent = :places_root WHERE id IN " + 1.360 + "(SELECT folder_id FROM moz_bookmarks_roots " + 1.361 + "WHERE folder_id <> :places_root)"); 1.362 + fixPlacesRootChildren.params["places_root"] = PlacesUtils.placesRootId; 1.363 + cleanupStatements.push(fixPlacesRootChildren); 1.364 + } 1.365 + selectPlacesRoot.finalize(); 1.366 + 1.367 + // C.2 fix roots titles 1.368 + // some alpha version has wrong roots title, and this also fixes them if 1.369 + // locale has changed. 1.370 + let updateRootTitleSql = "UPDATE moz_bookmarks SET title = :title " + 1.371 + "WHERE id = :root_id AND title <> :title"; 1.372 + // root 1.373 + let fixPlacesRootTitle = DBConn.createAsyncStatement(updateRootTitleSql); 1.374 + fixPlacesRootTitle.params["root_id"] = PlacesUtils.placesRootId; 1.375 + fixPlacesRootTitle.params["title"] = ""; 1.376 + cleanupStatements.push(fixPlacesRootTitle); 1.377 + // bookmarks menu 1.378 + let fixBookmarksMenuTitle = DBConn.createAsyncStatement(updateRootTitleSql); 1.379 + fixBookmarksMenuTitle.params["root_id"] = PlacesUtils.bookmarksMenuFolderId; 1.380 + fixBookmarksMenuTitle.params["title"] = 1.381 + PlacesUtils.getString("BookmarksMenuFolderTitle"); 1.382 + cleanupStatements.push(fixBookmarksMenuTitle); 1.383 + // bookmarks toolbar 1.384 + let fixBookmarksToolbarTitle = DBConn.createAsyncStatement(updateRootTitleSql); 1.385 + fixBookmarksToolbarTitle.params["root_id"] = PlacesUtils.toolbarFolderId; 1.386 + fixBookmarksToolbarTitle.params["title"] = 1.387 + PlacesUtils.getString("BookmarksToolbarFolderTitle"); 1.388 + cleanupStatements.push(fixBookmarksToolbarTitle); 1.389 + // unsorted bookmarks 1.390 + let fixUnsortedBookmarksTitle = DBConn.createAsyncStatement(updateRootTitleSql); 1.391 + fixUnsortedBookmarksTitle.params["root_id"] = PlacesUtils.unfiledBookmarksFolderId; 1.392 + fixUnsortedBookmarksTitle.params["title"] = 1.393 + PlacesUtils.getString("UnsortedBookmarksFolderTitle"); 1.394 + cleanupStatements.push(fixUnsortedBookmarksTitle); 1.395 + // tags 1.396 + let fixTagsRootTitle = DBConn.createAsyncStatement(updateRootTitleSql); 1.397 + fixTagsRootTitle.params["root_id"] = PlacesUtils.tagsFolderId; 1.398 + fixTagsRootTitle.params["title"] = 1.399 + PlacesUtils.getString("TagsFolderTitle"); 1.400 + cleanupStatements.push(fixTagsRootTitle); 1.401 + 1.402 + // MOZ_BOOKMARKS 1.403 + // D.1 remove items without a valid place 1.404 + // if fk IS NULL we fix them in D.7 1.405 + let deleteNoPlaceItems = DBConn.createAsyncStatement( 1.406 + "DELETE FROM moz_bookmarks WHERE id NOT IN ( " + 1.407 + "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots 1.408 + ") AND id IN (" + 1.409 + "SELECT b.id FROM moz_bookmarks b " + 1.410 + "WHERE fk NOT NULL AND b.type = :bookmark_type " + 1.411 + "AND NOT EXISTS (SELECT url FROM moz_places WHERE id = b.fk LIMIT 1) " + 1.412 + ")"); 1.413 + deleteNoPlaceItems.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; 1.414 + cleanupStatements.push(deleteNoPlaceItems); 1.415 + 1.416 + // D.2 remove items that are not uri bookmarks from tag containers 1.417 + let deleteBogusTagChildren = DBConn.createAsyncStatement( 1.418 + "DELETE FROM moz_bookmarks WHERE id NOT IN ( " + 1.419 + "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots 1.420 + ") AND id IN (" + 1.421 + "SELECT b.id FROM moz_bookmarks b " + 1.422 + "WHERE b.parent IN " + 1.423 + "(SELECT id FROM moz_bookmarks WHERE parent = :tags_folder) " + 1.424 + "AND b.type <> :bookmark_type " + 1.425 + ")"); 1.426 + deleteBogusTagChildren.params["tags_folder"] = PlacesUtils.tagsFolderId; 1.427 + deleteBogusTagChildren.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; 1.428 + cleanupStatements.push(deleteBogusTagChildren); 1.429 + 1.430 + // D.3 remove empty tags 1.431 + let deleteEmptyTags = DBConn.createAsyncStatement( 1.432 + "DELETE FROM moz_bookmarks WHERE id NOT IN ( " + 1.433 + "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots 1.434 + ") AND id IN (" + 1.435 + "SELECT b.id FROM moz_bookmarks b " + 1.436 + "WHERE b.id IN " + 1.437 + "(SELECT id FROM moz_bookmarks WHERE parent = :tags_folder) " + 1.438 + "AND NOT EXISTS " + 1.439 + "(SELECT id from moz_bookmarks WHERE parent = b.id LIMIT 1) " + 1.440 + ")"); 1.441 + deleteEmptyTags.params["tags_folder"] = PlacesUtils.tagsFolderId; 1.442 + cleanupStatements.push(deleteEmptyTags); 1.443 + 1.444 + // D.4 move orphan items to unsorted folder 1.445 + let fixOrphanItems = DBConn.createAsyncStatement( 1.446 + "UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN ( " + 1.447 + "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots 1.448 + ") AND id IN (" + 1.449 + "SELECT b.id FROM moz_bookmarks b " + 1.450 + "WHERE b.parent <> 0 " + // exclude Places root 1.451 + "AND NOT EXISTS " + 1.452 + "(SELECT id FROM moz_bookmarks WHERE id = b.parent LIMIT 1) " + 1.453 + ")"); 1.454 + fixOrphanItems.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId; 1.455 + cleanupStatements.push(fixOrphanItems); 1.456 + 1.457 + // D.5 fix wrong keywords 1.458 + let fixInvalidKeywords = DBConn.createAsyncStatement( 1.459 + "UPDATE moz_bookmarks SET keyword_id = NULL WHERE id NOT IN ( " + 1.460 + "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots 1.461 + ") AND id IN ( " + 1.462 + "SELECT id FROM moz_bookmarks b " + 1.463 + "WHERE keyword_id NOT NULL " + 1.464 + "AND NOT EXISTS " + 1.465 + "(SELECT id FROM moz_keywords WHERE id = b.keyword_id LIMIT 1) " + 1.466 + ")"); 1.467 + cleanupStatements.push(fixInvalidKeywords); 1.468 + 1.469 + // D.6 fix wrong item types 1.470 + // Folders and separators should not have an fk. 1.471 + // If they have a valid fk convert them to bookmarks. Later in D.9 we 1.472 + // will move eventual children to unsorted bookmarks. 1.473 + let fixBookmarksAsFolders = DBConn.createAsyncStatement( 1.474 + "UPDATE moz_bookmarks SET type = :bookmark_type WHERE id NOT IN ( " + 1.475 + "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots 1.476 + ") AND id IN ( " + 1.477 + "SELECT id FROM moz_bookmarks b " + 1.478 + "WHERE type IN (:folder_type, :separator_type) " + 1.479 + "AND fk NOTNULL " + 1.480 + ")"); 1.481 + fixBookmarksAsFolders.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; 1.482 + fixBookmarksAsFolders.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER; 1.483 + fixBookmarksAsFolders.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR; 1.484 + cleanupStatements.push(fixBookmarksAsFolders); 1.485 + 1.486 + // D.7 fix wrong item types 1.487 + // Bookmarks should have an fk, if they don't have any, convert them to 1.488 + // folders. 1.489 + let fixFoldersAsBookmarks = DBConn.createAsyncStatement( 1.490 + "UPDATE moz_bookmarks SET type = :folder_type WHERE id NOT IN ( " + 1.491 + "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots 1.492 + ") AND id IN ( " + 1.493 + "SELECT id FROM moz_bookmarks b " + 1.494 + "WHERE type = :bookmark_type " + 1.495 + "AND fk IS NULL " + 1.496 + ")"); 1.497 + fixFoldersAsBookmarks.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; 1.498 + fixFoldersAsBookmarks.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER; 1.499 + cleanupStatements.push(fixFoldersAsBookmarks); 1.500 + 1.501 + // D.9 fix wrong parents 1.502 + // Items cannot have separators or other bookmarks 1.503 + // as parent, if they have bad parent move them to unsorted bookmarks. 1.504 + let fixInvalidParents = DBConn.createAsyncStatement( 1.505 + "UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN ( " + 1.506 + "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots 1.507 + ") AND id IN ( " + 1.508 + "SELECT id FROM moz_bookmarks b " + 1.509 + "WHERE EXISTS " + 1.510 + "(SELECT id FROM moz_bookmarks WHERE id = b.parent " + 1.511 + "AND type IN (:bookmark_type, :separator_type) " + 1.512 + "LIMIT 1) " + 1.513 + ")"); 1.514 + fixInvalidParents.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId; 1.515 + fixInvalidParents.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK; 1.516 + fixInvalidParents.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR; 1.517 + cleanupStatements.push(fixInvalidParents); 1.518 + 1.519 + // D.10 recalculate positions 1.520 + // This requires multiple related statements. 1.521 + // We can detect a folder with bad position values comparing the sum of 1.522 + // all distinct position values (+1 since position is 0-based) with the 1.523 + // triangular numbers obtained by the number of children (n). 1.524 + // SUM(DISTINCT position + 1) == (n * (n + 1) / 2). 1.525 + cleanupStatements.push(DBConn.createAsyncStatement( 1.526 + "CREATE TEMP TABLE IF NOT EXISTS moz_bm_reindex_temp ( " + 1.527 + " id INTEGER PRIMARY_KEY " + 1.528 + ", parent INTEGER " + 1.529 + ", position INTEGER " + 1.530 + ") " 1.531 + )); 1.532 + cleanupStatements.push(DBConn.createAsyncStatement( 1.533 + "INSERT INTO moz_bm_reindex_temp " + 1.534 + "SELECT id, parent, 0 " + 1.535 + "FROM moz_bookmarks b " + 1.536 + "WHERE parent IN ( " + 1.537 + "SELECT parent " + 1.538 + "FROM moz_bookmarks " + 1.539 + "GROUP BY parent " + 1.540 + "HAVING (SUM(DISTINCT position + 1) - (count(*) * (count(*) + 1) / 2)) <> 0 " + 1.541 + ") " + 1.542 + "ORDER BY parent ASC, position ASC, ROWID ASC " 1.543 + )); 1.544 + cleanupStatements.push(DBConn.createAsyncStatement( 1.545 + "CREATE INDEX IF NOT EXISTS moz_bm_reindex_temp_index " + 1.546 + "ON moz_bm_reindex_temp(parent)" 1.547 + )); 1.548 + cleanupStatements.push(DBConn.createAsyncStatement( 1.549 + "UPDATE moz_bm_reindex_temp SET position = ( " + 1.550 + "ROWID - (SELECT MIN(t.ROWID) FROM moz_bm_reindex_temp t " + 1.551 + "WHERE t.parent = moz_bm_reindex_temp.parent) " + 1.552 + ") " 1.553 + )); 1.554 + cleanupStatements.push(DBConn.createAsyncStatement( 1.555 + "CREATE TEMP TRIGGER IF NOT EXISTS moz_bm_reindex_temp_trigger " + 1.556 + "BEFORE DELETE ON moz_bm_reindex_temp " + 1.557 + "FOR EACH ROW " + 1.558 + "BEGIN " + 1.559 + "UPDATE moz_bookmarks SET position = OLD.position WHERE id = OLD.id; " + 1.560 + "END " 1.561 + )); 1.562 + cleanupStatements.push(DBConn.createAsyncStatement( 1.563 + "DELETE FROM moz_bm_reindex_temp " 1.564 + )); 1.565 + cleanupStatements.push(DBConn.createAsyncStatement( 1.566 + "DROP INDEX moz_bm_reindex_temp_index " 1.567 + )); 1.568 + cleanupStatements.push(DBConn.createAsyncStatement( 1.569 + "DROP TRIGGER moz_bm_reindex_temp_trigger " 1.570 + )); 1.571 + cleanupStatements.push(DBConn.createAsyncStatement( 1.572 + "DROP TABLE moz_bm_reindex_temp " 1.573 + )); 1.574 + 1.575 + // D.12 Fix empty-named tags. 1.576 + // Tags were allowed to have empty names due to a UI bug. Fix them 1.577 + // replacing their title with "(notitle)". 1.578 + let fixEmptyNamedTags = DBConn.createAsyncStatement( 1.579 + "UPDATE moz_bookmarks SET title = :empty_title " + 1.580 + "WHERE length(title) = 0 AND type = :folder_type " + 1.581 + "AND parent = :tags_folder" 1.582 + ); 1.583 + fixEmptyNamedTags.params["empty_title"] = "(notitle)"; 1.584 + fixEmptyNamedTags.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER; 1.585 + fixEmptyNamedTags.params["tags_folder"] = PlacesUtils.tagsFolderId; 1.586 + cleanupStatements.push(fixEmptyNamedTags); 1.587 + 1.588 + // MOZ_FAVICONS 1.589 + // E.1 remove orphan icons 1.590 + let deleteOrphanIcons = DBConn.createAsyncStatement( 1.591 + "DELETE FROM moz_favicons WHERE id IN (" + 1.592 + "SELECT id FROM moz_favicons f " + 1.593 + "WHERE NOT EXISTS " + 1.594 + "(SELECT id FROM moz_places WHERE favicon_id = f.id LIMIT 1) " + 1.595 + ")"); 1.596 + cleanupStatements.push(deleteOrphanIcons); 1.597 + 1.598 + // MOZ_HISTORYVISITS 1.599 + // F.1 remove orphan visits 1.600 + let deleteOrphanVisits = DBConn.createAsyncStatement( 1.601 + "DELETE FROM moz_historyvisits WHERE id IN (" + 1.602 + "SELECT id FROM moz_historyvisits v " + 1.603 + "WHERE NOT EXISTS " + 1.604 + "(SELECT id FROM moz_places WHERE id = v.place_id LIMIT 1) " + 1.605 + ")"); 1.606 + cleanupStatements.push(deleteOrphanVisits); 1.607 + 1.608 + // MOZ_INPUTHISTORY 1.609 + // G.1 remove orphan input history 1.610 + let deleteOrphanInputHistory = DBConn.createAsyncStatement( 1.611 + "DELETE FROM moz_inputhistory WHERE place_id IN (" + 1.612 + "SELECT place_id FROM moz_inputhistory i " + 1.613 + "WHERE NOT EXISTS " + 1.614 + "(SELECT id FROM moz_places WHERE id = i.place_id LIMIT 1) " + 1.615 + ")"); 1.616 + cleanupStatements.push(deleteOrphanInputHistory); 1.617 + 1.618 + // MOZ_ITEMS_ANNOS 1.619 + // H.1 remove item annos with an invalid attribute 1.620 + let deleteInvalidAttributeItemsAnnos = DBConn.createAsyncStatement( 1.621 + "DELETE FROM moz_items_annos WHERE id IN ( " + 1.622 + "SELECT id FROM moz_items_annos t " + 1.623 + "WHERE NOT EXISTS " + 1.624 + "(SELECT id FROM moz_anno_attributes " + 1.625 + "WHERE id = t.anno_attribute_id LIMIT 1) " + 1.626 + ")"); 1.627 + cleanupStatements.push(deleteInvalidAttributeItemsAnnos); 1.628 + 1.629 + // H.2 remove orphan item annos 1.630 + let deleteOrphanItemsAnnos = DBConn.createAsyncStatement( 1.631 + "DELETE FROM moz_items_annos WHERE id IN ( " + 1.632 + "SELECT id FROM moz_items_annos t " + 1.633 + "WHERE NOT EXISTS " + 1.634 + "(SELECT id FROM moz_bookmarks WHERE id = t.item_id LIMIT 1) " + 1.635 + ")"); 1.636 + cleanupStatements.push(deleteOrphanItemsAnnos); 1.637 + 1.638 + // MOZ_KEYWORDS 1.639 + // I.1 remove unused keywords 1.640 + let deleteUnusedKeywords = DBConn.createAsyncStatement( 1.641 + "DELETE FROM moz_keywords WHERE id IN ( " + 1.642 + "SELECT id FROM moz_keywords k " + 1.643 + "WHERE NOT EXISTS " + 1.644 + "(SELECT id FROM moz_bookmarks WHERE keyword_id = k.id LIMIT 1) " + 1.645 + ")"); 1.646 + cleanupStatements.push(deleteUnusedKeywords); 1.647 + 1.648 + // MOZ_PLACES 1.649 + // L.1 fix wrong favicon ids 1.650 + let fixInvalidFaviconIds = DBConn.createAsyncStatement( 1.651 + "UPDATE moz_places SET favicon_id = NULL WHERE id IN ( " + 1.652 + "SELECT id FROM moz_places h " + 1.653 + "WHERE favicon_id NOT NULL " + 1.654 + "AND NOT EXISTS " + 1.655 + "(SELECT id FROM moz_favicons WHERE id = h.favicon_id LIMIT 1) " + 1.656 + ")"); 1.657 + cleanupStatements.push(fixInvalidFaviconIds); 1.658 + 1.659 + // L.2 recalculate visit_count and last_visit_date 1.660 + let fixVisitStats = DBConn.createAsyncStatement( 1.661 + "UPDATE moz_places " + 1.662 + "SET visit_count = (SELECT count(*) FROM moz_historyvisits " + 1.663 + "WHERE place_id = moz_places.id AND visit_type NOT IN (0,4,7,8)), " + 1.664 + "last_visit_date = (SELECT MAX(visit_date) FROM moz_historyvisits " + 1.665 + "WHERE place_id = moz_places.id) " + 1.666 + "WHERE id IN ( " + 1.667 + "SELECT h.id FROM moz_places h " + 1.668 + "WHERE visit_count <> (SELECT count(*) FROM moz_historyvisits v " + 1.669 + "WHERE v.place_id = h.id AND visit_type NOT IN (0,4,7,8)) " + 1.670 + "OR last_visit_date <> (SELECT MAX(visit_date) FROM moz_historyvisits v " + 1.671 + "WHERE v.place_id = h.id) " + 1.672 + ")"); 1.673 + cleanupStatements.push(fixVisitStats); 1.674 + 1.675 + // L.3 recalculate hidden for redirects. 1.676 + let fixRedirectsHidden = DBConn.createAsyncStatement( 1.677 + "UPDATE moz_places " + 1.678 + "SET hidden = 1 " + 1.679 + "WHERE id IN ( " + 1.680 + "SELECT h.id FROM moz_places h " + 1.681 + "JOIN moz_historyvisits src ON src.place_id = h.id " + 1.682 + "JOIN moz_historyvisits dst ON dst.from_visit = src.id AND dst.visit_type IN (5,6) " + 1.683 + "LEFT JOIN moz_bookmarks on fk = h.id AND fk ISNULL " + 1.684 + "GROUP BY src.place_id HAVING count(*) = visit_count " + 1.685 + ")"); 1.686 + cleanupStatements.push(fixRedirectsHidden); 1.687 + 1.688 + // MAINTENANCE STATEMENTS SHOULD GO ABOVE THIS POINT! 1.689 + 1.690 + return cleanupStatements; 1.691 + }, 1.692 + 1.693 + /** 1.694 + * Tries to vacuum the database. 1.695 + * 1.696 + * @param [optional] aTasks 1.697 + * Tasks object to execute. 1.698 + */ 1.699 + vacuum: function PDBU_vacuum(aTasks) 1.700 + { 1.701 + let tasks = new Tasks(aTasks); 1.702 + tasks.log("> Vacuum"); 1.703 + 1.704 + let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); 1.705 + DBFile.append("places.sqlite"); 1.706 + tasks.log("Initial database size is " + 1.707 + parseInt(DBFile.fileSize / 1024) + " KiB"); 1.708 + 1.709 + let stmt = DBConn.createAsyncStatement("VACUUM"); 1.710 + stmt.executeAsync({ 1.711 + handleError: PlacesDBUtils._handleError, 1.712 + handleResult: function () {}, 1.713 + 1.714 + handleCompletion: function (aReason) 1.715 + { 1.716 + if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { 1.717 + tasks.log("+ The database has been vacuumed"); 1.718 + let vacuumedDBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); 1.719 + vacuumedDBFile.append("places.sqlite"); 1.720 + tasks.log("Final database size is " + 1.721 + parseInt(vacuumedDBFile.fileSize / 1024) + " KiB"); 1.722 + } 1.723 + else { 1.724 + tasks.log("- Unable to vacuum database"); 1.725 + tasks.clear(); 1.726 + } 1.727 + 1.728 + PlacesDBUtils._executeTasks(tasks); 1.729 + } 1.730 + }); 1.731 + stmt.finalize(); 1.732 + }, 1.733 + 1.734 + /** 1.735 + * Forces a full expiration on the database. 1.736 + * 1.737 + * @param [optional] aTasks 1.738 + * Tasks object to execute. 1.739 + */ 1.740 + expire: function PDBU_expire(aTasks) 1.741 + { 1.742 + let tasks = new Tasks(aTasks); 1.743 + tasks.log("> Orphans expiration"); 1.744 + 1.745 + let expiration = Cc["@mozilla.org/places/expiration;1"]. 1.746 + getService(Ci.nsIObserver); 1.747 + 1.748 + Services.obs.addObserver(function (aSubject, aTopic, aData) { 1.749 + Services.obs.removeObserver(arguments.callee, aTopic); 1.750 + tasks.log("+ Database cleaned up"); 1.751 + PlacesDBUtils._executeTasks(tasks); 1.752 + }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false); 1.753 + 1.754 + // Force an orphans expiration step. 1.755 + expiration.observe(null, "places-debug-start-expiration", 0); 1.756 + }, 1.757 + 1.758 + /** 1.759 + * Collects statistical data on the database. 1.760 + * 1.761 + * @param [optional] aTasks 1.762 + * Tasks object to execute. 1.763 + */ 1.764 + stats: function PDBU_stats(aTasks) 1.765 + { 1.766 + let tasks = new Tasks(aTasks); 1.767 + tasks.log("> Statistics"); 1.768 + 1.769 + let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); 1.770 + DBFile.append("places.sqlite"); 1.771 + tasks.log("Database size is " + parseInt(DBFile.fileSize / 1024) + " KiB"); 1.772 + 1.773 + [ "user_version" 1.774 + , "page_size" 1.775 + , "cache_size" 1.776 + , "journal_mode" 1.777 + , "synchronous" 1.778 + ].forEach(function (aPragma) { 1.779 + let stmt = DBConn.createStatement("PRAGMA " + aPragma); 1.780 + stmt.executeStep(); 1.781 + tasks.log(aPragma + " is " + stmt.getString(0)); 1.782 + stmt.finalize(); 1.783 + }); 1.784 + 1.785 + // Get maximum number of unique URIs. 1.786 + try { 1.787 + let limitURIs = Services.prefs.getIntPref( 1.788 + "places.history.expiration.transient_current_max_pages"); 1.789 + tasks.log("History can store a maximum of " + limitURIs + " unique pages"); 1.790 + } catch(ex) {} 1.791 + 1.792 + let stmt = DBConn.createStatement( 1.793 + "SELECT name FROM sqlite_master WHERE type = :type"); 1.794 + stmt.params.type = "table"; 1.795 + while (stmt.executeStep()) { 1.796 + let tableName = stmt.getString(0); 1.797 + let countStmt = DBConn.createStatement( 1.798 + "SELECT count(*) FROM " + tableName); 1.799 + countStmt.executeStep(); 1.800 + tasks.log("Table " + tableName + " has " + countStmt.getInt32(0) + " records"); 1.801 + countStmt.finalize(); 1.802 + } 1.803 + stmt.reset(); 1.804 + 1.805 + stmt.params.type = "index"; 1.806 + while (stmt.executeStep()) { 1.807 + tasks.log("Index " + stmt.getString(0)); 1.808 + } 1.809 + stmt.reset(); 1.810 + 1.811 + stmt.params.type = "trigger"; 1.812 + while (stmt.executeStep()) { 1.813 + tasks.log("Trigger " + stmt.getString(0)); 1.814 + } 1.815 + stmt.finalize(); 1.816 + 1.817 + PlacesDBUtils._executeTasks(tasks); 1.818 + }, 1.819 + 1.820 + /** 1.821 + * Collects telemetry data. 1.822 + * 1.823 + * There are essentially two modes of collection and the mode is 1.824 + * determined by the presence of aHealthReportCallback. If 1.825 + * aHealthReportCallback is not defined (the default) then we are in 1.826 + * "Telemetry" mode. Results will be reported to Telemetry. If we are 1.827 + * in "Health Report" mode only the probes with a true healthreport 1.828 + * flag will be collected and the results will be reported to the 1.829 + * aHealthReportCallback. 1.830 + * 1.831 + * @param [optional] aTasks 1.832 + * Tasks object to execute. 1.833 + * @param [optional] aHealthReportCallback 1.834 + * Function to receive data relevant for Firefox Health Report. 1.835 + */ 1.836 + telemetry: function PDBU_telemetry(aTasks, aHealthReportCallback=null) 1.837 + { 1.838 + let tasks = new Tasks(aTasks); 1.839 + 1.840 + let isTelemetry = !aHealthReportCallback; 1.841 + 1.842 + // This will be populated with one integer property for each probe result, 1.843 + // using the histogram name as key. 1.844 + let probeValues = {}; 1.845 + 1.846 + // The following array contains an ordered list of entries that are 1.847 + // processed to collect telemetry data. Each entry has these properties: 1.848 + // 1.849 + // histogram: Name of the telemetry histogram to update. 1.850 + // query: This is optional. If present, contains a database command 1.851 + // that will be executed asynchronously, and whose result will 1.852 + // be added to the telemetry histogram. 1.853 + // callback: This is optional. If present, contains a function that must 1.854 + // return the value that will be added to the telemetry 1.855 + // histogram. If a query is also present, its result is passed 1.856 + // as the first argument of the function. If the function 1.857 + // raises an exception, no data is added to the histogram. 1.858 + // healthreport: Boolean indicating whether this probe is relevant 1.859 + // to Firefox Health Report. 1.860 + // 1.861 + // Since all queries are executed in order by the database backend, the 1.862 + // callbacks can also use the result of previous queries stored in the 1.863 + // probeValues object. 1.864 + let probes = [ 1.865 + { histogram: "PLACES_PAGES_COUNT", 1.866 + healthreport: true, 1.867 + query: "SELECT count(*) FROM moz_places" }, 1.868 + 1.869 + { histogram: "PLACES_BOOKMARKS_COUNT", 1.870 + healthreport: true, 1.871 + query: "SELECT count(*) FROM moz_bookmarks b " 1.872 + + "JOIN moz_bookmarks t ON t.id = b.parent " 1.873 + + "AND t.parent <> :tags_folder " 1.874 + + "WHERE b.type = :type_bookmark " }, 1.875 + 1.876 + { histogram: "PLACES_TAGS_COUNT", 1.877 + query: "SELECT count(*) FROM moz_bookmarks " 1.878 + + "WHERE parent = :tags_folder " }, 1.879 + 1.880 + { histogram: "PLACES_FOLDERS_COUNT", 1.881 + query: "SELECT count(*) FROM moz_bookmarks " 1.882 + + "WHERE TYPE = :type_folder " 1.883 + + "AND parent NOT IN (0, :places_root, :tags_folder) " }, 1.884 + 1.885 + { histogram: "PLACES_KEYWORDS_COUNT", 1.886 + query: "SELECT count(*) FROM moz_keywords " }, 1.887 + 1.888 + { histogram: "PLACES_SORTED_BOOKMARKS_PERC", 1.889 + query: "SELECT IFNULL(ROUND(( " 1.890 + + "SELECT count(*) FROM moz_bookmarks b " 1.891 + + "JOIN moz_bookmarks t ON t.id = b.parent " 1.892 + + "AND t.parent <> :tags_folder AND t.parent > :places_root " 1.893 + + "WHERE b.type = :type_bookmark " 1.894 + + ") * 100 / ( " 1.895 + + "SELECT count(*) FROM moz_bookmarks b " 1.896 + + "JOIN moz_bookmarks t ON t.id = b.parent " 1.897 + + "AND t.parent <> :tags_folder " 1.898 + + "WHERE b.type = :type_bookmark " 1.899 + + ")), 0) " }, 1.900 + 1.901 + { histogram: "PLACES_TAGGED_BOOKMARKS_PERC", 1.902 + query: "SELECT IFNULL(ROUND(( " 1.903 + + "SELECT count(*) FROM moz_bookmarks b " 1.904 + + "JOIN moz_bookmarks t ON t.id = b.parent " 1.905 + + "AND t.parent = :tags_folder " 1.906 + + ") * 100 / ( " 1.907 + + "SELECT count(*) FROM moz_bookmarks b " 1.908 + + "JOIN moz_bookmarks t ON t.id = b.parent " 1.909 + + "AND t.parent <> :tags_folder " 1.910 + + "WHERE b.type = :type_bookmark " 1.911 + + ")), 0) " }, 1.912 + 1.913 + { histogram: "PLACES_DATABASE_FILESIZE_MB", 1.914 + callback: function () { 1.915 + let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); 1.916 + DBFile.append("places.sqlite"); 1.917 + return parseInt(DBFile.fileSize / BYTES_PER_MEBIBYTE); 1.918 + } 1.919 + }, 1.920 + 1.921 + { histogram: "PLACES_DATABASE_JOURNALSIZE_MB", 1.922 + callback: function () { 1.923 + let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); 1.924 + DBFile.append("places.sqlite-wal"); 1.925 + return parseInt(DBFile.fileSize / BYTES_PER_MEBIBYTE); 1.926 + } 1.927 + }, 1.928 + 1.929 + { histogram: "PLACES_DATABASE_PAGESIZE_B", 1.930 + query: "PRAGMA page_size /* PlacesDBUtils.jsm PAGESIZE_B */" }, 1.931 + 1.932 + { histogram: "PLACES_DATABASE_SIZE_PER_PAGE_B", 1.933 + query: "PRAGMA page_count", 1.934 + callback: function (aDbPageCount) { 1.935 + // Note that the database file size would not be meaningful for this 1.936 + // calculation, because the file grows in fixed-size chunks. 1.937 + let dbPageSize = probeValues.PLACES_DATABASE_PAGESIZE_B; 1.938 + let placesPageCount = probeValues.PLACES_PAGES_COUNT; 1.939 + return Math.round((dbPageSize * aDbPageCount) / placesPageCount); 1.940 + } 1.941 + }, 1.942 + 1.943 + { histogram: "PLACES_ANNOS_BOOKMARKS_COUNT", 1.944 + query: "SELECT count(*) FROM moz_items_annos" }, 1.945 + 1.946 + // LENGTH is not a perfect measure, since it returns the number of bytes 1.947 + // only for BLOBs, the number of chars for anything else. Though it's 1.948 + // the best approximation we have. 1.949 + { histogram: "PLACES_ANNOS_BOOKMARKS_SIZE_KB", 1.950 + query: "SELECT SUM(LENGTH(content))/1024 FROM moz_items_annos" }, 1.951 + 1.952 + { histogram: "PLACES_ANNOS_PAGES_COUNT", 1.953 + query: "SELECT count(*) FROM moz_annos" }, 1.954 + 1.955 + { histogram: "PLACES_ANNOS_PAGES_SIZE_KB", 1.956 + query: "SELECT SUM(LENGTH(content))/1024 FROM moz_annos" }, 1.957 + ]; 1.958 + 1.959 + let params = { 1.960 + tags_folder: PlacesUtils.tagsFolderId, 1.961 + type_folder: PlacesUtils.bookmarks.TYPE_FOLDER, 1.962 + type_bookmark: PlacesUtils.bookmarks.TYPE_BOOKMARK, 1.963 + places_root: PlacesUtils.placesRootId 1.964 + }; 1.965 + 1.966 + let outstandingProbes = 0; 1.967 + 1.968 + function reportResult(aProbe, aValue) { 1.969 + outstandingProbes--; 1.970 + 1.971 + let value = aValue; 1.972 + try { 1.973 + if ("callback" in aProbe) { 1.974 + value = aProbe.callback(value); 1.975 + } 1.976 + probeValues[aProbe.histogram] = value; 1.977 + Services.telemetry.getHistogramById(aProbe.histogram).add(value); 1.978 + } catch (ex) { 1.979 + Components.utils.reportError("Error adding value " + value + 1.980 + " to histogram " + aProbe.histogram + 1.981 + ": " + ex); 1.982 + } 1.983 + 1.984 + if (!outstandingProbes && aHealthReportCallback) { 1.985 + try { 1.986 + aHealthReportCallback(probeValues); 1.987 + } catch (ex) { 1.988 + Components.utils.reportError(ex); 1.989 + } 1.990 + } 1.991 + } 1.992 + 1.993 + for (let i = 0; i < probes.length; i++) { 1.994 + let probe = probes[i]; 1.995 + 1.996 + if (!isTelemetry && !probe.healthreport) { 1.997 + continue; 1.998 + } 1.999 + 1.1000 + outstandingProbes++; 1.1001 + 1.1002 + if (!("query" in probe)) { 1.1003 + reportResult(probe); 1.1004 + continue; 1.1005 + } 1.1006 + 1.1007 + let stmt = DBConn.createAsyncStatement(probe.query); 1.1008 + for (param in params) { 1.1009 + if (probe.query.indexOf(":" + param) > 0) { 1.1010 + stmt.params[param] = params[param]; 1.1011 + } 1.1012 + } 1.1013 + 1.1014 + try { 1.1015 + stmt.executeAsync({ 1.1016 + handleError: PlacesDBUtils._handleError, 1.1017 + handleResult: function (aResultSet) { 1.1018 + let row = aResultSet.getNextRow(); 1.1019 + reportResult(probe, row.getResultByIndex(0)); 1.1020 + }, 1.1021 + handleCompletion: function () {} 1.1022 + }); 1.1023 + } finally{ 1.1024 + stmt.finalize(); 1.1025 + } 1.1026 + } 1.1027 + 1.1028 + PlacesDBUtils._executeTasks(tasks); 1.1029 + }, 1.1030 + 1.1031 + /** 1.1032 + * Runs a list of tasks, notifying log messages to the callback. 1.1033 + * 1.1034 + * @param aTasks 1.1035 + * Array of tasks to be executed, in form of pointers to methods in 1.1036 + * this module. 1.1037 + * @param [optional] aCallback 1.1038 + * Callback to be invoked when done. It will receive an array of 1.1039 + * log messages. 1.1040 + */ 1.1041 + runTasks: function PDBU_runTasks(aTasks, aCallback) { 1.1042 + let tasks = new Tasks(aTasks); 1.1043 + tasks.callback = aCallback; 1.1044 + PlacesDBUtils._executeTasks(tasks); 1.1045 + } 1.1046 +}; 1.1047 + 1.1048 +/** 1.1049 + * LIFO tasks stack. 1.1050 + * 1.1051 + * @param [optional] aTasks 1.1052 + * Array of tasks or another Tasks object to clone. 1.1053 + */ 1.1054 +function Tasks(aTasks) 1.1055 +{ 1.1056 + if (aTasks) { 1.1057 + if (Array.isArray(aTasks)) { 1.1058 + this._list = aTasks.slice(0, aTasks.length); 1.1059 + } 1.1060 + // This supports passing in a Tasks-like object, with a "list" property, 1.1061 + // for compatibility reasons. 1.1062 + else if (typeof(aTasks) == "object" && 1.1063 + (Tasks instanceof Tasks || "list" in aTasks)) { 1.1064 + this._list = aTasks.list; 1.1065 + this._log = aTasks.messages; 1.1066 + this.callback = aTasks.callback; 1.1067 + this.scope = aTasks.scope; 1.1068 + this._telemetryStart = aTasks._telemetryStart; 1.1069 + } 1.1070 + } 1.1071 +} 1.1072 + 1.1073 +Tasks.prototype = { 1.1074 + _list: [], 1.1075 + _log: [], 1.1076 + callback: null, 1.1077 + scope: null, 1.1078 + _telemetryStart: 0, 1.1079 + 1.1080 + /** 1.1081 + * Adds a task to the top of the list. 1.1082 + * 1.1083 + * @param aNewElt 1.1084 + * Task to be added. 1.1085 + */ 1.1086 + push: function T_push(aNewElt) 1.1087 + { 1.1088 + this._list.unshift(aNewElt); 1.1089 + }, 1.1090 + 1.1091 + /** 1.1092 + * Returns and consumes next task. 1.1093 + * 1.1094 + * @return next task or undefined if no task is left. 1.1095 + */ 1.1096 + pop: function T_pop() this._list.shift(), 1.1097 + 1.1098 + /** 1.1099 + * Removes all tasks. 1.1100 + */ 1.1101 + clear: function T_clear() 1.1102 + { 1.1103 + this._list.length = 0; 1.1104 + }, 1.1105 + 1.1106 + /** 1.1107 + * Returns array of tasks ordered from the next to be run to the latest. 1.1108 + */ 1.1109 + get list() this._list.slice(0, this._list.length), 1.1110 + 1.1111 + /** 1.1112 + * Adds a message to the log. 1.1113 + * 1.1114 + * @param aMsg 1.1115 + * String message to be added. 1.1116 + */ 1.1117 + log: function T_log(aMsg) 1.1118 + { 1.1119 + this._log.push(aMsg); 1.1120 + }, 1.1121 + 1.1122 + /** 1.1123 + * Returns array of log messages ordered from oldest to newest. 1.1124 + */ 1.1125 + get messages() this._log.slice(0, this._log.length), 1.1126 +}