Bug 1199077 - Add Places APIs to store the history sync ID and last sync time. r?mak,markh draft
authorKit Cambridge <kit@yakshaving.ninja>
Sat, 03 Mar 2018 11:29:35 -0800
changeset 765151 700ac20b3d6487635a158606e18f9b6d346eecdd
parent 765150 e31ace2f78c79f1bd304f9992ec44fb8dfe8a9a9
child 765152 5cad4d987db789d3bfe575691d9242a1ad44588a
push id101984
push userbmo:kit@mozilla.com
push dateFri, 09 Mar 2018 06:22:41 +0000
reviewersmak, markh
bugs1199077
milestone60.0a1
Bug 1199077 - Add Places APIs to store the history sync ID and last sync time. r?mak,markh MozReview-Commit-ID: 7yEGZl5pIru
toolkit/components/places/PlacesSyncUtils.jsm
toolkit/components/places/tests/sync/test_sync_utils.js
--- 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();
+});