Bug 610501: handle smart bookmarks correctly in Sync.
authorRichard Newman <rnewman@mozilla.com>
Wed, 26 Jan 2011 12:36:38 -0800
changeset 61480 1c9bdb06d07af6863735848255907f0f223ae4fb
parent 61479 9bff5fa7cab3833d9a4fb79f28b2cccb16aa227c
child 61481 eff3225f51e95d6f610dd9a306de44cc3e9cd31d
push id1
push userroot
push dateTue, 10 Dec 2013 15:46:25 +0000
bugs610501
Bug 610501: handle smart bookmarks correctly in Sync.
services/sync/modules/engines/bookmarks.js
services/sync/tests/unit/test_bookmark_smart_bookmarks.js
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -45,16 +45,17 @@ const EXPORTED_SYMBOLS = ['BookmarksEngi
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const GUID_ANNO = "sync/guid";
 const MOBILE_ANNO = "mobile/bookmarksRoot";
 const PARENT_ANNO = "sync/parent";
 const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
+const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
 const FOLDER_SORTINDEX = 1000000;
 
 try {
   Cu.import("resource://gre/modules/PlacesUtils.jsm");
 }
 catch(ex) {
   Cu.import("resource://gre/modules/utils.js");
 }
@@ -132,17 +133,18 @@ Utils.deferGetSet(BookmarkMicsum, "clear
 function BookmarkQuery(collection, id) {
   Bookmark.call(this, collection, id, "query");
 }
 BookmarkQuery.prototype = {
   __proto__: Bookmark.prototype,
   _logName: "Record.BookmarkQuery",
 };
 
-Utils.deferGetSet(BookmarkQuery, "cleartext", ["folderName"]);
+Utils.deferGetSet(BookmarkQuery, "cleartext", ["folderName",
+                                               "queryId"]);
 
 function BookmarkFolder(collection, id, type) {
   PlacesItem.call(this, collection, id, type || "folder");
 }
 BookmarkFolder.prototype = {
   __proto__: PlacesItem.prototype,
   _logName: "Record.Folder",
 };
@@ -580,16 +582,21 @@ BookmarksStore.prototype = {
     case "query":
     case "microsummary": {
       let uri = Utils.makeURI(record.bmkUri);
       newId = this._bms.insertBookmark(record._parent, uri,
                                        Svc.Bookmark.DEFAULT_INDEX, record.title);
       this._log.debug(["created bookmark", newId, "under", record._parent,
                        "as", record.title, record.bmkUri].join(" "));
 
+      // Smart bookmark annotations are strings.
+      if (record.queryId) {
+        Utils.anno(newId, SMART_BOOKMARKS_ANNO, record.queryId);
+      }
+
       if (Utils.isArray(record.tags)) {
         this._tagURI(uri, record.tags);
       }
       this._bms.setKeywordForBookmark(newId, record.keyword);
       if (record.description)
         Utils.anno(newId, "bookmarkProperties/description", record.description);
 
       if (record.loadInSidebar)
@@ -730,16 +737,19 @@ BookmarksStore.prototype = {
           else {
             let micsum = this._ms.createMicrosummary(micsumURI, genURI);
             this._ms.setMicrosummary(itemId, micsum);
           }
         } catch (e) {
           this._log.debug("Could not set microsummary generator URI: " + e);
         }
       } break;
+      case "queryId":
+        Utils.anno(itemId, SMART_BOOKMARKS_ANNO, val);
+        break;
       case "siteUri":
         this._ls.setSiteURI(itemId, Utils.makeURI(val));
         break;
       case "feedUri":
         this._ls.setFeedURI(itemId, Utils.makeURI(val));
         break;
       }
     }
@@ -879,17 +889,28 @@ BookmarksStore.prototype = {
 
           // Get the actual tag name instead of the local itemId
           let folder = bmkUri.match(/[:&]folder=(\d+)/);
           try {
             // There might not be the tag yet when creating on a new client
             if (folder != null) {
               folder = folder[1];
               record.folderName = this._bms.getItemTitle(folder);
-              this._log.debug("query id: " + folder + " = " + record.folderName);
+              this._log.trace("query id: " + folder + " = " + record.folderName);
+            }
+          }
+          catch(ex) {}
+          
+          // Persist the Smart Bookmark anno, if found.
+          try {
+            let anno = Utils.anno(placeId, SMART_BOOKMARKS_ANNO);
+            if (anno != null) {
+              this._log.trace("query anno: " + SMART_BOOKMARKS_ANNO +
+                              " = " + anno);
+              record.queryId = anno;
             }
           }
           catch(ex) {}
         }
         else
           record = new Bookmark(collection, id);
         record.title = this._bms.getItemTitle(placeId);
       }
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js
@@ -0,0 +1,188 @@
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/engines/bookmarks.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/log4moz.js");
+Cu.import("resource://services-sync/util.js");
+
+Cu.import("resource://services-sync/service.js");
+try {
+  Cu.import("resource://gre/modules/PlacesUtils.jsm");
+}
+catch(ex) {
+  Cu.import("resource://gre/modules/utils.js");
+}
+
+const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
+var IOService = Cc["@mozilla.org/network/io-service;1"]
+                .getService(Ci.nsIIOService);
+("http://www.mozilla.com", null, null);
+
+
+Engines.register(BookmarksEngine);
+let engine = Engines.get("bookmarks");
+let store = engine._store;
+
+// Clean up after other tests. Only necessary in XULRunner.
+store.wipe();
+
+function makeEngine() {
+  return new BookmarksEngine();
+}
+var syncTesting = new SyncTestingInfrastructure(makeEngine);
+
+function newSmartBookmark(parent, uri, position, title, queryID) {
+  let id = PlacesUtils.bookmarks.insertBookmark(parent, uri, position, title);
+  PlacesUtils.annotations.setItemAnnotation(id, SMART_BOOKMARKS_ANNO,
+                                            queryID, 0,
+                                            PlacesUtils.annotations.EXPIRE_NEVER);
+  return id;
+}
+
+function smartBookmarkCount() {
+  // We do it this way because PlacesUtils.annotations.getItemsWithAnnotation
+  // doesn't work the same (or at all?) between 3.6 and 4.0.
+  let out = {};
+  Svc.Annos.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO, out);
+  return out.value;
+}
+
+function clearBookmarks() {
+  _("Cleaning up existing items.");
+  Svc.Bookmark.removeFolderChildren(Svc.Bookmark.bookmarksMenuFolder);
+  Svc.Bookmark.removeFolderChildren(Svc.Bookmark.tagsFolder);
+  Svc.Bookmark.removeFolderChildren(Svc.Bookmark.toolbarFolder);
+  Svc.Bookmark.removeFolderChildren(Svc.Bookmark.unfiledBookmarksFolder);
+  startCount = smartBookmarkCount();
+}
+  
+// Verify that Places smart bookmarks have their annotation uploaded and
+// handled locally.
+function test_annotation_uploaded() {
+  let startCount = smartBookmarkCount();
+  
+  _("Start count is " + startCount);
+  
+  if (startCount > 0) {
+    // This can happen in XULRunner.
+    clearBookmarks();
+    _("Start count is now " + startCount);
+  }
+
+  _("Create a smart bookmark in the toolbar.");
+  let parent = PlacesUtils.toolbarFolderId;
+  let uri =
+    Utils.makeURI("place:redirectsMode=" +
+                  Ci.nsINavHistoryQueryOptions.REDIRECTS_MODE_TARGET +
+                  "&sort=" +
+                  Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING +
+                  "&maxResults=10");
+  let title = "Most Visited";
+
+  let mostVisitedID = newSmartBookmark(parent, uri, -1, title, "MostVisited");
+
+  _("New item ID: " + mostVisitedID);
+  do_check_true(!!mostVisitedID);
+
+  let annoValue = Utils.anno(mostVisitedID, SMART_BOOKMARKS_ANNO);
+  _("Anno: " + annoValue);
+  do_check_eq("MostVisited", annoValue);
+
+  let guid = store.GUIDForId(mostVisitedID);
+  _("GUID: " + guid);
+  do_check_true(!!guid);
+
+  _("Create record object and verify that it's sane.");
+  let record = store.createRecord(guid);
+  do_check_true(record instanceof Bookmark);
+  do_check_true(record instanceof BookmarkQuery);
+
+  do_check_eq(record.bmkUri, uri.spec);
+
+  _("Make sure the new record carries with it the annotation.");
+  do_check_eq("MostVisited", record.queryId);
+
+  _("Our count has increased since we started.");
+  do_check_eq(smartBookmarkCount(), startCount + 1);
+
+  _("Sync record to the server.");
+  Svc.Prefs.set("username", "foo");
+  Service.serverURL = "http://localhost:8080/";
+  Service.clusterURL = "http://localhost:8080/";
+
+  let collection = new ServerCollection({}, true);
+  let global = new ServerWBO('global',
+                             {engines: {bookmarks: {version: engine.version,
+                                                    syncID: engine.syncID}}});
+  let server = httpd_setup({
+    "/1.0/foo/storage/meta/global": global.handler(),
+    "/1.0/foo/storage/bookmarks": collection.handler()
+  });
+
+  try {
+    engine.sync();
+    let wbos = [id for ([id, wbo] in Iterator(collection.wbos))
+                   if (["menu", "toolbar", "mobile"].indexOf(id) == -1)];
+    do_check_eq(wbos.length, 1);
+
+    _("Verify that the server WBO has the annotation.");
+    let serverGUID = wbos[0];
+    do_check_eq(serverGUID, guid);
+    let serverWBO = collection.wbos[serverGUID];
+    do_check_true(!!serverWBO);
+    let body = JSON.parse(JSON.parse(serverWBO.payload).ciphertext);
+    do_check_eq(body.queryId, "MostVisited");
+
+    _("We still have the right count.");
+    do_check_eq(smartBookmarkCount(), startCount + 1);
+
+    _("Clear local records; now we can't find it.");
+    
+    // "Clear" by changing attributes: if we delete it, apparently it sticks
+    // around as a deleted record...
+    Svc.Bookmark.setItemGUID(mostVisitedID, "abcdefabcdef");
+    Svc.Bookmark.setItemTitle(mostVisitedID, "Not Most Visited");
+    Svc.Bookmark.changeBookmarkURI(mostVisitedID,
+                                   Utils.makeURI("http://something/else"));
+    Svc.Annos.removeItemAnnotation(mostVisitedID, SMART_BOOKMARKS_ANNO);
+    store.wipe();
+    engine.resetClient();
+    do_check_eq(smartBookmarkCount(), startCount);
+
+    _("Sync. Verify that the downloaded record carries the annotation.");
+    engine.sync();
+
+    _("Verify that the Places DB now has an annotated bookmark.");
+    _("Our count has increased again.");
+    do_check_eq(smartBookmarkCount(), startCount + 1);
+
+    _("Find by GUID and verify that it's annotated.");
+    let newID = store.idForGUID(serverGUID);
+    let newAnnoValue = Utils.anno(newID, SMART_BOOKMARKS_ANNO);
+    do_check_eq(newAnnoValue, "MostVisited");
+    do_check_eq(Svc.Bookmark.getBookmarkURI(newID).spec, uri.spec);
+    
+    _("Test updating.");
+    let newRecord = store.createRecord(serverGUID);
+    do_check_eq(newRecord.queryId, newAnnoValue);
+    newRecord.queryId = "LeastVisited";
+    store.update(newRecord);
+    do_check_eq("LeastVisited", Utils.anno(newID, SMART_BOOKMARKS_ANNO));
+    
+
+  } finally {
+    // Clean up.
+    store.wipe();
+    server.stop(do_test_finished);
+    Svc.Prefs.resetBranch("");
+    Records.clearCache();
+  }
+}
+
+function run_test() {
+  initTestLogging("Trace");
+  Log4Moz.repository.getLogger("Engine.Bookmarks").level = Log4Moz.Level.Trace;
+
+  CollectionKeys.generateNewKeys();
+
+  test_annotation_uploaded();
+}