Bug 1199077 - Add Places APIs to store the history sync ID and last sync time. r?mak,markh
MozReview-Commit-ID: 7yEGZl5pIru
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -70,17 +70,130 @@ XPCOMUtils.defineLazyGetter(this, "ROOT_
[PlacesUtils.bookmarks.unfiledGuid]: "unfiled",
[PlacesUtils.bookmarks.mobileGuid]: "mobile",
}));
XPCOMUtils.defineLazyGetter(this, "ROOTS", () =>
Object.keys(ROOT_RECORD_ID_TO_GUID)
);
-PlacesSyncUtils.history = Object.freeze({
+const HistorySyncUtils = PlacesSyncUtils.history = Object.freeze({
+ SYNC_ID_META_KEY: "sync/history/syncId",
+ LAST_SYNC_META_KEY: "sync/history/lastSync",
+
+ /**
+ * Returns the current history sync ID, or `""` if one isn't set.
+ */
+ async getSyncId() {
+ let syncId = await PlacesUtils.metadata.get(
+ HistorySyncUtils.SYNC_ID_META_KEY);
+ return syncId || "";
+ },
+
+ /**
+ * Assigns a new sync ID. This is called when we sync for the first time with
+ * a new account, and when we're the first to sync after a node reassignment.
+ *
+ * @return {Promise} resolved once the ID has been updated.
+ * @resolves to the new sync ID.
+ */
+ resetSyncId() {
+ return PlacesUtils.withConnectionWrapper(
+ "HistorySyncUtils: resetSyncId",
+ function(db) {
+ let newSyncId = PlacesUtils.history.makeGuid();
+ return db.executeTransaction(async function() {
+ await setHistorySyncId(db, newSyncId);
+ return newSyncId;
+ });
+ }
+ );
+ },
+
+ /**
+ * Ensures that the existing local sync ID, if any, is up-to-date with the
+ * server. This is called when we sync with an existing account.
+ *
+ * @param newSyncId
+ * The server's sync ID.
+ * @return {Promise} resolved once the ID has been updated.
+ */
+ async ensureCurrentSyncId(newSyncId) {
+ if (!newSyncId || typeof newSyncId != "string") {
+ throw new TypeError("Invalid new history sync ID");
+ }
+ await PlacesUtils.withConnectionWrapper(
+ "HistorySyncUtils: ensureCurrentSyncId",
+ async function(db) {
+ let existingSyncId = await PlacesUtils.metadata.getWithConnection(
+ db, HistorySyncUtils.SYNC_ID_META_KEY);
+
+ if (existingSyncId == newSyncId) {
+ HistorySyncLog.debug("History sync ID up-to-date",
+ { existingSyncId });
+ return;
+ }
+
+ HistorySyncLog.debug("History sync ID changed; resetting metadata",
+ { existingSyncId, newSyncId });
+ await db.executeTransaction(function() {
+ return setHistorySyncId(db, newSyncId);
+ });
+ }
+ );
+ },
+
+ /**
+ * Returns the last sync time, in seconds, for the history collection, or 0
+ * if history has never synced before.
+ */
+ async getLastSync() {
+ let lastSync = await PlacesUtils.metadata.get(
+ HistorySyncUtils.LAST_SYNC_META_KEY);
+ return lastSync ? lastSync / 1000 : 0;
+ },
+
+ /**
+ * Updates the history collection last sync time.
+ *
+ * @param lastSyncSeconds
+ * The collection last sync time, in seconds, as a number or string.
+ */
+ async setLastSync(lastSyncSeconds) {
+ let lastSync = Math.floor(lastSyncSeconds * 1000);
+ if (!Number.isInteger(lastSync)) {
+ throw new TypeError("Invalid history last sync timestamp");
+ }
+ await PlacesUtils.metadata.set(HistorySyncUtils.LAST_SYNC_META_KEY,
+ lastSync);
+ },
+
+ /**
+ * Removes all history visits and pages from the database. Sync calls this
+ * method when it receives a command from a remote client to wipe all stored
+ * data.
+ *
+ * @return {Promise} resolved once all pages and visits have been removed.
+ */
+ async wipe() {
+ await PlacesUtils.history.clear();
+ await HistorySyncUtils.reset();
+ },
+
+ /**
+ * Removes the sync ID and last sync time for the history collection. Unlike
+ * `wipe`, this keeps all existing history pages and visits.
+ *
+ * @return {Promise} resolved once the metadata have been removed.
+ */
+ reset() {
+ return PlacesUtils.metadata.delete(HistorySyncUtils.SYNC_ID_META_KEY,
+ HistorySyncUtils.LAST_SYNC_META_KEY);
+ },
+
/**
* Clamps a history visit date between the current date and the earliest
* sensible date.
*
* @param {Date} visitDate
* The visit date.
* @return {Date} The clamped visit date.
*/
@@ -1226,16 +1339,20 @@ const BookmarkSyncUtils = PlacesSyncUtil
* given value.
*/
async fetchGuidsWithAnno(anno, val) {
let db = await PlacesUtils.promiseDBConnection();
return fetchGuidsWithAnno(db, anno, val);
},
});
+XPCOMUtils.defineLazyGetter(this, "HistorySyncLog", () => {
+ return Log.repository.getLogger("Sync.Engine.History.HistorySyncUtils");
+});
+
XPCOMUtils.defineLazyGetter(this, "BookmarkSyncLog", () => {
// Use a sub-log of the bookmarks engine, so setting the level for that
// engine also adjust the level of this log.
return Log.repository.getLogger("Sync.Engine.Bookmarks.BookmarkSyncUtils");
});
function validateSyncBookmarkObject(name, input, behavior) {
return PlacesUtils.validateItemProperties(name,
@@ -2433,16 +2550,25 @@ var removeTombstones = function(db, guid
function notify(observers, notification, args = []) {
for (let observer of observers) {
try {
observer[notification](...args);
} catch (ex) {}
}
}
+// Sets the history sync ID and clears the last sync time.
+async function setHistorySyncId(db, newSyncId) {
+ await PlacesUtils.metadata.setWithConnection(db,
+ HistorySyncUtils.SYNC_ID_META_KEY, newSyncId);
+
+ await PlacesUtils.metadata.deleteWithConnection(db,
+ HistorySyncUtils.LAST_SYNC_META_KEY);
+}
+
// Sets the bookmarks sync ID and clears the last sync time.
async function setBookmarksSyncId(db, newSyncId) {
await PlacesUtils.metadata.setWithConnection(db,
BookmarkSyncUtils.SYNC_ID_META_KEY, newSyncId);
await PlacesUtils.metadata.deleteWithConnection(db,
BookmarkSyncUtils.LAST_SYNC_META_KEY,
BookmarkSyncUtils.WIPE_REMOTE_META_KEY);
--- a/toolkit/components/places/tests/sync/test_sync_utils.js
+++ b/toolkit/components/places/tests/sync/test_sync_utils.js
@@ -3386,8 +3386,60 @@ add_task(async function test_bookmarks_e
ok(syncFields.every(field =>
field.syncStatus == PlacesUtils.bookmarks.SYNC_STATUS.UNKNOWN
), "Should reset all sync statuses to UNKNOWN after bookmarks sync ID mismatch");
}
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
+
+add_task(async function test_history_resetSyncId() {
+ let syncId = await PlacesSyncUtils.history.getSyncId();
+ strictEqual(syncId, "", "Should start with empty history sync ID");
+
+ info("Assign new history sync ID for first time");
+ let newSyncId = await PlacesSyncUtils.history.resetSyncId();
+ syncId = await PlacesSyncUtils.history.getSyncId();
+ equal(newSyncId, syncId,
+ "Should assign new history sync ID for first time");
+
+ info("Set history last sync time");
+ let lastSync = Date.now() / 1000;
+ await PlacesSyncUtils.history.setLastSync(lastSync);
+ equal(await PlacesSyncUtils.history.getLastSync(), lastSync,
+ "Should record history last sync time");
+
+ newSyncId = await PlacesSyncUtils.history.resetSyncId();
+ notEqual(newSyncId, syncId,
+ "Should set new history sync ID if one already exists");
+ strictEqual(await PlacesSyncUtils.history.getLastSync(), 0,
+ "Should reset history last sync time after resetting sync ID");
+
+ await PlacesSyncUtils.history.reset();
+});
+
+add_task(async function test_history_ensureCurrentSyncId() {
+ info("Assign new history sync ID");
+ await PlacesSyncUtils.history.ensureCurrentSyncId("syncIdAAAAAA");
+ equal(await PlacesSyncUtils.history.getSyncId(), "syncIdAAAAAA",
+ "Should assign history sync ID if one doesn't exist");
+
+ info("Ensure existing history sync ID matches");
+ let lastSync = Date.now() / 1000;
+ await PlacesSyncUtils.history.setLastSync(lastSync);
+ await PlacesSyncUtils.history.ensureCurrentSyncId("syncIdAAAAAA");
+
+ equal(await PlacesSyncUtils.history.getSyncId(), "syncIdAAAAAA",
+ "Should keep existing history sync ID on match");
+ equal(await PlacesSyncUtils.history.getLastSync(), lastSync,
+ "Should keep existing history last sync time on sync ID match");
+
+ info("Replace existing history sync ID with new ID");
+ await PlacesSyncUtils.history.ensureCurrentSyncId("syncIdBBBBBB");
+
+ equal(await PlacesSyncUtils.history.getSyncId(), "syncIdBBBBBB",
+ "Should replace existing history sync ID on mismatch");
+ strictEqual(await PlacesSyncUtils.history.getLastSync(), 0,
+ "Should reset history last sync time on sync ID mismatch");
+
+ await PlacesSyncUtils.history.reset();
+});