Bug 891303 - New Places Async Transaction manager (backend part. affects nothing for now). r=mak. sr=gavin

This commit is contained in:
Asaf Romano 2014-03-12 14:12:15 +02:00
parent 72f33bb3e4
commit caa37c32d3
7 changed files with 2403 additions and 37 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1523,7 +1523,31 @@ this.PlacesUtils = {
}
});
return deferred.promise;
}
},
/**
* Get the unique id for an item (a bookmark, a folder or a separator) given
* its item id.
*
* @param aItemId
* an item id
* @return {Promise}
* @resolves to the GUID.
* @rejects if aItemId is invalid.
*/
promiseItemGUID: function (aItemId) GUIDHelper.getItemGUID(aItemId),
/**
* Get the item id for an item (a bookmark, a folder or a separator) given
* its unique id.
*
* @param aGUID
* an item GUID
* @retrun {Promise}
* @resolves to the GUID.
* @rejects if there's no item for the given GUID.
*/
promiseItemId: function (aGUID) GUIDHelper.getItemId(aGUID)
};
/**
@ -1640,6 +1664,138 @@ XPCOMUtils.defineLazyServiceGetter(this, "focusManager",
"@mozilla.org/focus-manager;1",
"nsIFocusManager");
// Sometime soon, likely as part of the transition to mozIAsyncBookmarks,
// itemIds will be deprecated in favour of GUIDs, which play much better
// with multiple undo/redo operations. Because these GUIDs are already stored,
// and because we don't want to revise the transactions API once more when this
// happens, transactions are set to work with GUIDs exclusively, in the sense
// that they may never expose itemIds, nor do they accept them as input.
// More importantly, transactions which add or remove items guarantee to
// restore the guids on undo/redo, so that the following transactions that may
// done or undo can assume the items they're interested in are stil accessible
// through the same GUID.
// The current bookmarks API, however, doesn't expose the necessary means for
// working with GUIDs. So, until it does, this helper object accesses the
// Places database directly in order to switch between GUIDs and itemIds, and
// "restore" GUIDs on items re-created items.
const REASON_FINISHED = Ci.mozIStorageStatementCallback.REASON_FINISHED;
let GUIDHelper = {
// Cache for guid<->itemId paris.
GUIDsForIds: new Map(),
idsForGUIDs: new Map(),
getItemId: function (aGUID) {
if (this.idsForGUIDs.has(aGUID))
return Promise.resolve(this.idsForGUIDs.get(aGUID));
let deferred = Promise.defer();
let itemId = -1;
this._getIDStatement.params.guid = aGUID;
this._getIDStatement.executeAsync({
handleResult: function (aResultSet) {
let row = aResultSet.getNextRow();
if (row)
itemId = row.getResultByIndex(0);
},
handleCompletion: function (aReason) {
if (aReason == REASON_FINISHED && itemId != -1) {
deferred.resolve(itemId);
this.ensureObservingRemovedItems();
this.idsForGUIDs.set(aGUID, itemId);
}
else if (itemId != -1) {
deferred.reject("no item found for the given guid");
}
else {
deferred.reject("SQLite Error: " + aReason);
}
}
});
return deferred.promise;
},
getItemGUID: function (aItemId) {
if (this.GUIDsForIds.has(aItemId))
return Promise.resolve(this.GUIDsForIds.has(aItemId));
let deferred = Promise.defer();
let guid = "";
this._getGUIDStatement.params.id = aItemId;
this._getGUIDStatement.executeAsync({
handleResult: function (aResultSet) {
let row = aResultSet.getNextRow();
if (row) {
guid = row.getResultByIndex(1);
}
},
handleCompletion: function (aReason) {
if (aReason == REASON_FINISHED && guid) {
deferred.resolve(guid);
this.ensureObservingRemovedItems();
this.GUIDsForIds.set(aItemId, guid);
}
else if (!guid) {
deferred.reject("no item found for the given itemId");
}
else {
deferred.reject("SQLite Error: " + aReason);
}
}
});
return deferred.promise;
},
ensureObservingRemovedItems: function () {
if (!("observer" in this)) {
/**
* This observers serves two purposes:
* (1) Invalidate cached id<->guid paris on when items are removed.
* (2) Cache GUIDs given us free of charge by onItemAdded/onItemRemoved.
* So, for exmaple, when the NewBookmark needs the new GUID, we already
* have it cached.
*/
this.observer = {
onItemAdded: (aItemId, aParentId, aIndex, aItemType, aURI, aTitle,
aDateAdded, aGUID, aParentGUID) => {
this.GUIDsForIds.set(aItemId, aGUID);
this.GUIDsForIds.set(aParentId, aParentGUID);
},
onItemRemoved:
(aItemId, aParentId, aIndex, aItemTyep, aURI, aGUID, aParentGUID) => {
this.GUIDsForIds.delete(aItemId);
this.idsForGUIDs.delete(aGUID);
this.GUIDsForIds.set(aParentId, aParentGUID);
},
QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),
__noSuchMethod__: () => {}, // Catch all all onItem* methods.
};
PlacesUtils.bookmarks.addObserver(this.observer, false);
PlacesUtils.registerShutdownFunction(() => {
PlacesUtils.bookmarks.removeObserver(this.observer);
});
}
}
};
XPCOMUtils.defineLazyGetter(GUIDHelper, "_getIDStatement", () => {
let s = PlacesUtils.history.DBConnection.createAsyncStatement(
"SELECT b.id, b.guid from moz_bookmarks b WHERE b.guid = :guid");
PlacesUtils.registerShutdownFunction( () => s.finalize() );
return s;
});
XPCOMUtils.defineLazyGetter(GUIDHelper, "_getGUIDStatement", () => {
let s = PlacesUtils.history.DBConnection.createAsyncStatement(
"SELECT b.id, b.guid from moz_bookmarks b WHERE b.id = :id");
PlacesUtils.registerShutdownFunction( () => s.finalize() );
return s;
});
////////////////////////////////////////////////////////////////////////////////
//// Transactions handlers.

View File

@ -67,6 +67,7 @@ if CONFIG['MOZ_PLACES']:
'ColorConversion.js',
'PlacesBackups.jsm',
'PlacesDBUtils.jsm',
'PlacesTransactions.jsm',
]
EXTRA_PP_JS_MODULES += [

View File

@ -126,9 +126,9 @@ LivemarkService.prototype = {
stmt.finalize();
},
_onCacheReady: function LS__onCacheReady(aCallback, aWaitForAsyncWrites)
_onCacheReady: function LS__onCacheReady(aCallback)
{
if (this._pendingStmt || aWaitForAsyncWrites) {
if (this._pendingStmt) {
// The cache is still being populated, so enqueue the job to the Storage
// async thread. Ideally this should just dispatch a runnable to it,
// that would call back on the main thread, but bug 608142 made that
@ -235,9 +235,7 @@ LivemarkService.prototype = {
});
if (this._itemAdded && this._itemAdded.id == livemark.id) {
livemark.index = this._itemAdded.index;
if (!aLivemarkInfo.guid) {
livemark.guid = this._itemAdded.guid;
}
livemark.guid = this._itemAdded.guid;
if (!aLivemarkInfo.lastModified) {
livemark.lastModified = this._itemAdded.lastModified;
}
@ -246,7 +244,7 @@ LivemarkService.prototype = {
// Updating the cache even if it has not yet been populated doesn't
// matter since it will just be overwritten.
this._livemarks[livemark.id] = livemark;
this._guids[aLivemarkInfo.guid] = livemark.id;
this._guids[livemark.guid] = livemark.id;
}
catch (ex) {
addLivemarkEx = ex;
@ -272,7 +270,7 @@ LivemarkService.prototype = {
}
deferred.resolve(livemark);
}
}, true);
});
}
return deferred.promise;
@ -558,11 +556,9 @@ function Livemark(aLivemarkInfo)
// Create a new livemark.
this.id = PlacesUtils.bookmarks.createFolder(aLivemarkInfo.parentId,
aLivemarkInfo.title,
aLivemarkInfo.index);
aLivemarkInfo.index,
aLivemarkInfo.guid);
PlacesUtils.bookmarks.setFolderReadonly(this.id, true);
if (aLivemarkInfo.guid) {
this.writeGuid(aLivemarkInfo.guid);
}
this.writeFeedURI(aLivemarkInfo.feedURI);
if (aLivemarkInfo.siteURI) {
this.writeSiteURI(aLivemarkInfo.siteURI);
@ -630,31 +626,6 @@ Livemark.prototype = {
this.siteURI = aSiteURI;
},
writeGuid: function LM_writeGuid(aGUID)
{
// There isn't a way to create a bookmark with a given guid yet, nor to
// set a guid on an existing one. So, for now, just go the dirty way.
let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.DBConnection;
let stmt = db.createAsyncStatement("UPDATE moz_bookmarks " +
"SET guid = :guid " +
"WHERE id = :item_id");
stmt.params.guid = aGUID;
stmt.params.item_id = this.id;
let livemark = this;
stmt.executeAsync({
handleError: function () {},
handleResult: function () {},
handleCompletion: function ETAT_handleCompletion(aReason)
{
if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
livemark._guid = aGUID;
}
}
});
stmt.finalize();
},
set guid(aGUID) {
this._guid = aGUID;
return aGUID;

View File

@ -36,6 +36,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
"resource://gre/modules/BookmarkJSONUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
"resource://gre/modules/PlacesBackups.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
"resource://gre/modules/PlacesTransactions.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");

File diff suppressed because it is too large Load Diff

View File

@ -138,3 +138,4 @@ skip-if = os == "android"
[test_telemetry.js]
[test_getPlacesInfo.js]
[test_pageGuid_bookmarkGuid.js]
[test_async_transactions.js]