diff options
Diffstat (limited to 'services/cloudsync/CloudSyncPlacesWrapper.jsm')
-rw-r--r-- | services/cloudsync/CloudSyncPlacesWrapper.jsm | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/services/cloudsync/CloudSyncPlacesWrapper.jsm b/services/cloudsync/CloudSyncPlacesWrapper.jsm new file mode 100644 index 000000000..dd8c5c52e --- /dev/null +++ b/services/cloudsync/CloudSyncPlacesWrapper.jsm @@ -0,0 +1,375 @@ +/* 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(); |