/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; this.EXPORTED_SYMBOLS = ["PlacesWrapper"]; const {interfaces: Ci, utils: Cu} = Components; const REASON_ERROR = Ci.mozIStorageStatementCallback.REASON_ERROR; Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://gre/modules/PlacesUtils.jsm"); Cu.import("resource:///modules/PlacesUIUtils.jsm"); Cu.import("resource://services-common/utils.js"); var PlacesQueries = function () { } PlacesQueries.prototype = { cachedStmts: {}, getQuery: function (queryString) { if (queryString in this.cachedStmts) { return this.cachedStmts[queryString]; } let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; return this.cachedStmts[queryString] = db.createAsyncStatement(queryString); } }; var PlacesWrapper = function () { } PlacesWrapper.prototype = { placesQueries: new PlacesQueries(), guidToLocalId: function (guid) { let deferred = Promise.defer(); let stmt = "SELECT id AS item_id " + "FROM moz_bookmarks " + "WHERE guid = :guid"; let query = this.placesQueries.getQuery(stmt); function getLocalId(results) { let result = results[0] && results[0]["item_id"]; return Promise.resolve(result); } query.params.guid = guid.toString(); this.asyncQuery(query, ["item_id"]) .then(getLocalId, deferred.reject) .then(deferred.resolve, deferred.reject); return deferred.promise; }, localIdToGuid: function (id) { let deferred = Promise.defer(); let stmt = "SELECT guid " + "FROM moz_bookmarks " + "WHERE id = :item_id"; let query = this.placesQueries.getQuery(stmt); function getGuid(results) { let result = results[0] && results[0]["guid"]; return Promise.resolve(result); } query.params.item_id = id; this.asyncQuery(query, ["guid"]) .then(getGuid, deferred.reject) .then(deferred.resolve, deferred.reject); return deferred.promise; }, getItemsById: function (ids, types) { let deferred = Promise.defer(); let stmt = "SELECT b.id, b.type, b.parent, b.position, b.title, b.guid, b.dateAdded, b.lastModified, p.url " + "FROM moz_bookmarks b " + "LEFT JOIN moz_places p ON b.fk = p.id " + "WHERE b.id in (" + ids.join(",") + ") AND b.type in (" + types.join(",") + ")"; let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; let query = db.createAsyncStatement(stmt); this.asyncQuery(query, ["id", "type", "parent", "position", "title", "guid", "dateAdded", "lastModified", "url"]) .then(deferred.resolve, deferred.reject); return deferred.promise; }, getItemsByParentId: function (parents, types) { let deferred = Promise.defer(); let stmt = "SELECT b.id, b.type, b.parent, b.position, b.title, b.guid, b.dateAdded, b.lastModified, p.url " + "FROM moz_bookmarks b " + "LEFT JOIN moz_places p ON b.fk = p.id " + "WHERE b.parent in (" + parents.join(",") + ") AND b.type in (" + types.join(",") + ")"; let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; let query = db.createAsyncStatement(stmt); this.asyncQuery(query, ["id", "type", "parent", "position", "title", "guid", "dateAdded", "lastModified", "url"]) .then(deferred.resolve, deferred.reject); return deferred.promise; }, getItemsByGuid: function (guids, types) { let deferred = Promise.defer(); guids = guids.map(JSON.stringify); let stmt = "SELECT b.id, b.type, b.parent, b.position, b.title, b.guid, b.dateAdded, b.lastModified, p.url " + "FROM moz_bookmarks b " + "LEFT JOIN moz_places p ON b.fk = p.id " + "WHERE b.guid in (" + guids.join(",") + ") AND b.type in (" + types.join(",") + ")"; let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; let query = db.createAsyncStatement(stmt); this.asyncQuery(query, ["id", "type", "parent", "position", "title", "guid", "dateAdded", "lastModified", "url"]) .then(deferred.resolve, deferred.reject); return deferred.promise; }, updateCachedFolderIds: function (folderCache, folder) { let deferred = Promise.defer(); let stmt = "SELECT id, guid " + "FROM moz_bookmarks " + "WHERE parent = :parent_id AND type = :item_type"; let query = this.placesQueries.getQuery(stmt); query.params.parent_id = folder; query.params.item_type = PlacesUtils.bookmarks.TYPE_FOLDER; this.asyncQuery(query, ["id", "guid"]).then( function (items) { let previousIds = folderCache.getChildren(folder); let currentIds = new Set(); for (let item of items) { currentIds.add(item.id); } let newIds = new Set(); let missingIds = new Set(); for (let currentId of currentIds) { if (!previousIds.has(currentId)) { newIds.add(currentId); } } for (let previousId of previousIds) { if (!currentIds.has(previousId)) { missingIds.add(previousId); } } folderCache.setChildren(folder, currentIds); let promises = []; for (let newId of newIds) { promises.push(this.updateCachedFolderIds(folderCache, newId)); } Promise.all(promises) .then(deferred.resolve, deferred.reject); for (let missingId of missingIds) { folderCache.remove(missingId); } }.bind(this) ); return deferred.promise; }, getLocalIdsWithAnnotation: function (anno) { let deferred = Promise.defer(); let stmt = "SELECT a.item_id " + "FROM moz_anno_attributes n " + "JOIN moz_items_annos a ON n.id = a.anno_attribute_id " + "WHERE n.name = :anno_name"; let query = this.placesQueries.getQuery(stmt); query.params.anno_name = anno.toString(); this.asyncQuery(query, ["item_id"]) .then(function (items) { let results = []; for (let item of items) { results.push(item.item_id); } deferred.resolve(results); }, deferred.reject); return deferred.promise; }, getItemAnnotationsForLocalId: function (id) { let deferred = Promise.defer(); let stmt = "SELECT a.name, b.content " + "FROM moz_anno_attributes a " + "JOIN moz_items_annos b ON a.id = b.anno_attribute_id " + "WHERE b.item_id = :item_id"; let query = this.placesQueries.getQuery(stmt); query.params.item_id = id; this.asyncQuery(query, ["name", "content"]) .then(function (results) { let annos = {}; for (let result of results) { annos[result.name] = result.content; } deferred.resolve(annos); }, deferred.reject); return deferred.promise; }, insertBookmark: function (parent, uri, index, title, guid) { let parsedURI; try { parsedURI = CommonUtils.makeURI(uri) } catch (e) { return Promise.reject("unable to parse URI '" + uri + "': " + e); } try { let id = PlacesUtils.bookmarks.insertBookmark(parent, parsedURI, index, title, guid); return Promise.resolve(id); } catch (e) { return Promise.reject("unable to insert bookmark " + JSON.stringify(arguments) + ": " + e); } }, setItemAnnotation: function (item, anno, value, flags, exp) { try { return Promise.resolve(PlacesUtils.annotations.setItemAnnotation(item, anno, value, flags, exp)); } catch (e) { return Promise.reject(e); } }, itemHasAnnotation: function (item, anno) { try { return Promise.resolve(PlacesUtils.annotations.itemHasAnnotation(item, anno)); } catch (e) { return Promise.reject(e); } }, createFolder: function (parent, name, index, guid) { try { return Promise.resolve(PlacesUtils.bookmarks.createFolder(parent, name, index, guid)); } catch (e) { return Promise.reject("unable to create folder ['" + name + "']: " + e); } }, removeFolderChildren: function (folder) { try { PlacesUtils.bookmarks.removeFolderChildren(folder); return Promise.resolve(); } catch (e) { return Promise.reject(e); } }, insertSeparator: function (parent, index, guid) { try { return Promise.resolve(PlacesUtils.bookmarks.insertSeparator(parent, index, guid)); } catch (e) { return Promise.reject(e); } }, removeItem: function (item) { try { return Promise.resolve(PlacesUtils.bookmarks.removeItem(item)); } catch (e) { return Promise.reject(e); } }, setItemDateAdded: function (item, dateAdded) { try { return Promise.resolve(PlacesUtils.bookmarks.setItemDateAdded(item, dateAdded)); } catch (e) { return Promise.reject(e); } }, setItemLastModified: function (item, lastModified) { try { return Promise.resolve(PlacesUtils.bookmarks.setItemLastModified(item, lastModified)); } catch (e) { return Promise.reject(e); } }, setItemTitle: function (item, title) { try { return Promise.resolve(PlacesUtils.bookmarks.setItemTitle(item, title)); } catch (e) { return Promise.reject(e); } }, changeBookmarkURI: function (item, uri) { try { uri = CommonUtils.makeURI(uri); return Promise.resolve(PlacesUtils.bookmarks.changeBookmarkURI(item, uri)); } catch (e) { return Promise.reject(e); } }, moveItem: function (item, parent, index) { try { return Promise.resolve(PlacesUtils.bookmarks.moveItem(item, parent, index)); } catch (e) { return Promise.reject(e); } }, setItemIndex: function (item, index) { try { return Promise.resolve(PlacesUtils.bookmarks.setItemIndex(item, index)); } catch (e) { return Promise.reject(e); } }, asyncQuery: function (query, names) { let deferred = Promise.defer(); let storageCallback = { results: [], handleResult: function (results) { if (!names) { return; } let row; while ((row = results.getNextRow()) != null) { let item = {}; for (let name of names) { item[name] = row.getResultByName(name); } this.results.push(item); } }, handleError: function (error) { deferred.reject(error); }, handleCompletion: function (reason) { if (REASON_ERROR == reason) { return; } deferred.resolve(this.results); } }; query.executeAsync(storageCallback); return deferred.promise; }, }; this.PlacesWrapper = new PlacesWrapper();