~ubuntu-branches/ubuntu/oneiric/weave/oneiric

« back to all changes in this revision

Viewing changes to source/modules/engines/bookmarks.js

  • Committer: Bazaar Package Importer
  • Author(s): Micah Gersten
  • Date: 2010-08-11 00:35:15 UTC
  • mfrom: (3.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20100811003515-o3jbh826bnd1syjv
Tags: 1.4.3-1ubuntu1
* Add -fshort-wchar to CXXFLAGS to fix FTBFS in Ubuntu
  - update debian/rules 

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* ***** BEGIN LICENSE BLOCK *****
2
 
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3
 
 *
4
 
 * The contents of this file are subject to the Mozilla Public License Version
5
 
 * 1.1 (the "License"); you may not use this file except in compliance with
6
 
 * the License. You may obtain a copy of the License at
7
 
 * http://www.mozilla.org/MPL/
8
 
 *
9
 
 * Software distributed under the License is distributed on an "AS IS" basis,
10
 
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11
 
 * for the specific language governing rights and limitations under the
12
 
 * License.
13
 
 *
14
 
 * The Original Code is Bookmarks Sync.
15
 
 *
16
 
 * The Initial Developer of the Original Code is Mozilla.
17
 
 * Portions created by the Initial Developer are Copyright (C) 2007
18
 
 * the Initial Developer. All Rights Reserved.
19
 
 *
20
 
 * Contributor(s):
21
 
 *  Dan Mills <thunder@mozilla.com>
22
 
 *  Jono DiCarlo <jdicarlo@mozilla.org>
23
 
 *  Anant Narayanan <anant@kix.in>
24
 
 *
25
 
 * Alternatively, the contents of this file may be used under the terms of
26
 
 * either the GNU General Public License Version 2 or later (the "GPL"), or
27
 
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28
 
 * in which case the provisions of the GPL or the LGPL are applicable instead
29
 
 * of those above. If you wish to allow use of your version of this file only
30
 
 * under the terms of either the GPL or the LGPL, and not to allow others to
31
 
 * use your version of this file under the terms of the MPL, indicate your
32
 
 * decision by deleting the provisions above and replace them with the notice
33
 
 * and other provisions required by the GPL or the LGPL. If you do not delete
34
 
 * the provisions above, a recipient may use your version of this file under
35
 
 * the terms of any one of the MPL, the GPL or the LGPL.
36
 
 *
37
 
 * ***** END LICENSE BLOCK ***** */
38
 
 
39
 
const EXPORTED_SYMBOLS = ['BookmarksEngine', 'BookmarksSharingManager'];
40
 
 
41
 
const Cc = Components.classes;
42
 
const Ci = Components.interfaces;
43
 
const Cu = Components.utils;
44
 
 
45
 
const PARENT_ANNO = "weave/parent";
46
 
const PREDECESSOR_ANNO = "weave/predecessor";
47
 
const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
48
 
 
49
 
try {
50
 
  Cu.import("resource://gre/modules/PlacesUtils.jsm");
51
 
}
52
 
catch(ex) {
53
 
  Cu.import("resource://gre/modules/utils.js");
54
 
}
55
 
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
56
 
Cu.import("resource://weave/ext/Observers.js");
57
 
Cu.import("resource://weave/util.js");
58
 
Cu.import("resource://weave/engines.js");
59
 
Cu.import("resource://weave/stores.js");
60
 
Cu.import("resource://weave/trackers.js");
61
 
Cu.import("resource://weave/type_records/bookmark.js");
62
 
 
63
 
function archiveBookmarks() {
64
 
  // Some nightly builds of 3.7 don't have this function
65
 
  try {
66
 
    PlacesUtils.archiveBookmarksFile(null, true);
67
 
  }
68
 
  catch(ex) {}
69
 
}
70
 
 
71
 
// Lazily initialize the special top level folders
72
 
let kSpecialIds = {};
73
 
[["menu", "bookmarksMenuFolder"],
74
 
 ["places", "placesRoot"],
75
 
 ["tags", "tagsFolder"],
76
 
 ["toolbar", "toolbarFolder"],
77
 
 ["unfiled", "unfiledBookmarksFolder"],
78
 
].forEach(function([guid, placeName]) {
79
 
  Utils.lazy2(kSpecialIds, guid, function() Svc.Bookmark[placeName]);
80
 
});
81
 
Utils.lazy2(kSpecialIds, "mobile", function() {
82
 
  // Use the (one) mobile root if it already exists
83
 
  let anno = "mobile/bookmarksRoot";
84
 
  let root = Svc.Annos.getItemsWithAnnotation(anno, {});
85
 
  if (root.length != 0)
86
 
    return root[0];
87
 
 
88
 
  // Create the special mobile folder to store mobile bookmarks
89
 
  let mobile = Svc.Bookmark.createFolder(Svc.Bookmark.placesRoot, "mobile", -1);
90
 
  Utils.anno(mobile, anno, 1);
91
 
  return mobile;
92
 
});
93
 
 
94
 
// Create some helper functions to convert GUID/ids
95
 
function idForGUID(guid) {
96
 
  if (guid in kSpecialIds)
97
 
    return kSpecialIds[guid];
98
 
  return Svc.Bookmark.getItemIdForGUID(guid);
99
 
}
100
 
function GUIDForId(placeId) {
101
 
  for (let [guid, id] in Iterator(kSpecialIds))
102
 
    if (placeId == id)
103
 
      return guid;
104
 
  return Svc.Bookmark.getItemGUID(placeId);
105
 
}
106
 
 
107
 
function BookmarksEngine() {
108
 
  SyncEngine.call(this, "Bookmarks");
109
 
  this._handleImport();
110
 
}
111
 
BookmarksEngine.prototype = {
112
 
  __proto__: SyncEngine.prototype,
113
 
  _recordObj: PlacesItem,
114
 
  _storeObj: BookmarksStore,
115
 
  _trackerObj: BookmarksTracker,
116
 
 
117
 
  _handleImport: function _handleImport() {
118
 
    Observers.add("bookmarks-restore-begin", function() {
119
 
      this._log.debug("Ignoring changes from importing bookmarks");
120
 
      this._tracker.ignoreAll = true;
121
 
    }, this);
122
 
 
123
 
    Observers.add("bookmarks-restore-success", function() {
124
 
      this._log.debug("Tracking all items on successful import");
125
 
      this._tracker.ignoreAll = false;
126
 
 
127
 
      // Mark all the items as changed so they get uploaded
128
 
      for (let id in this._store.getAllIDs())
129
 
        this._tracker.addChangedID(id);
130
 
    }, this);
131
 
 
132
 
    Observers.add("bookmarks-restore-failed", function() {
133
 
      this._tracker.ignoreAll = false;
134
 
    }, this);
135
 
  },
136
 
 
137
 
  _sync: Utils.batchSync("Bookmark", SyncEngine),
138
 
 
139
 
  _syncStartup: function _syncStart() {
140
 
    SyncEngine.prototype._syncStartup.call(this);
141
 
 
142
 
    // For first-syncs, make a backup for the user to restore
143
 
    if (this.lastSync == 0)
144
 
      archiveBookmarks();
145
 
 
146
 
    // Lazily create a mapping of folder titles and separator positions to GUID
147
 
    this.__defineGetter__("_lazyMap", function() {
148
 
      delete this._lazyMap;
149
 
 
150
 
      let lazyMap = {};
151
 
      for (let guid in this._store.getAllIDs()) {
152
 
        // Figure out what key to store the mapping
153
 
        let key;
154
 
        let id = idForGUID(guid);
155
 
        switch (Svc.Bookmark.getItemType(id)) {
156
 
          case Svc.Bookmark.TYPE_BOOKMARK:
157
 
            key = "b" + Svc.Bookmark.getBookmarkURI(id).spec + ":" +
158
 
              Svc.Bookmark.getItemTitle(id);
159
 
            break;
160
 
          case Svc.Bookmark.TYPE_FOLDER:
161
 
            key = "f" + Svc.Bookmark.getItemTitle(id);
162
 
            break;
163
 
          case Svc.Bookmark.TYPE_SEPARATOR:
164
 
            key = "s" + Svc.Bookmark.getItemIndex(id);
165
 
            break;
166
 
          default:
167
 
            continue;
168
 
        }
169
 
 
170
 
        // The mapping is on a per parent-folder-name basis
171
 
        let parent = Svc.Bookmark.getFolderIdForItem(id);
172
 
        let parentName = Svc.Bookmark.getItemTitle(parent);
173
 
        if (lazyMap[parentName] == null)
174
 
          lazyMap[parentName] = {};
175
 
 
176
 
        // If the entry already exists, remember that there are explicit dupes
177
 
        let entry = new String(guid);
178
 
        entry.hasDupe = lazyMap[parentName][key] != null;
179
 
 
180
 
        // Remember this item's guid for its parent-name/key pair
181
 
        lazyMap[parentName][key] = entry;
182
 
        this._log.trace("Mapped: " + [parentName, key, entry, entry.hasDupe]);
183
 
      }
184
 
 
185
 
      // Expose a helper function to get a dupe guid for an item
186
 
      return this._lazyMap = function(item) {
187
 
        // Figure out if we have something to key with
188
 
        let key;
189
 
        switch (item.type) {
190
 
          case "bookmark":
191
 
          case "query":
192
 
          case "microsummary":
193
 
            key = "b" + item.bmkUri + ":" + item.title;
194
 
            break;
195
 
          case "folder":
196
 
          case "livemark":
197
 
            key = "f" + item.title;
198
 
            break;
199
 
          case "separator":
200
 
            key = "s" + item.pos;
201
 
            break;
202
 
          default:
203
 
            return;
204
 
        }
205
 
 
206
 
        // Give the guid if we have the matching pair
207
 
        this._log.trace("Finding mapping: " + item.parentName + ", " + key);
208
 
        let parent = lazyMap[item.parentName];
209
 
        let dupe = parent && parent[key];
210
 
        this._log.trace("Mapped dupe: " + dupe);
211
 
        return dupe;
212
 
      };
213
 
    });
214
 
  },
215
 
 
216
 
  _syncFinish: function _syncFinish() {
217
 
    SyncEngine.prototype._syncFinish.call(this);
218
 
    delete this._lazyMap;
219
 
    this._tracker._ensureMobileQuery();
220
 
  },
221
 
 
222
 
  _createRecord: function _createRecord(id) {
223
 
    // Create the record like normal but mark it as having dupes if necessary
224
 
    let record = SyncEngine.prototype._createRecord.call(this, id);
225
 
    let entry = this._lazyMap(record);
226
 
    if (entry != null && entry.hasDupe)
227
 
      record.hasDupe = true;
228
 
    return record;
229
 
  },
230
 
 
231
 
  _findDupe: function _findDupe(item) {
232
 
    // Don't bother finding a dupe if the incoming item has duplicates
233
 
    if (item.hasDupe)
234
 
      return;
235
 
    return this._lazyMap(item);
236
 
  },
237
 
 
238
 
  _handleDupe: function _handleDupe(item, dupeId) {
239
 
    // The local dupe has the lower id, so make it the winning id
240
 
    if (dupeId < item.id)
241
 
      [item.id, dupeId] = [dupeId, item.id];
242
 
 
243
 
    // Trigger id change from dupe to winning and update the server
244
 
    this._store.changeItemID(dupeId, item.id);
245
 
    this._deleteId(dupeId);
246
 
    this._tracker.addChangedID(item.id, 0);
247
 
  }
248
 
};
249
 
 
250
 
function BookmarksStore(name) {
251
 
  Store.call(this, name);
252
 
}
253
 
BookmarksStore.prototype = {
254
 
  __proto__: Store.prototype,
255
 
 
256
 
  __bms: null,
257
 
  get _bms() {
258
 
    if (!this.__bms)
259
 
      this.__bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
260
 
                   getService(Ci.nsINavBookmarksService);
261
 
    return this.__bms;
262
 
  },
263
 
 
264
 
  __hsvc: null,
265
 
  get _hsvc() {
266
 
    if (!this.__hsvc)
267
 
      this.__hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
268
 
                    getService(Ci.nsINavHistoryService);
269
 
    return this.__hsvc;
270
 
  },
271
 
 
272
 
  __ls: null,
273
 
  get _ls() {
274
 
    if (!this.__ls)
275
 
      this.__ls = Cc["@mozilla.org/browser/livemark-service;2"].
276
 
        getService(Ci.nsILivemarkService);
277
 
    return this.__ls;
278
 
  },
279
 
 
280
 
  get _ms() {
281
 
    let ms;
282
 
    try {
283
 
      ms = Cc["@mozilla.org/microsummary/service;1"].
284
 
        getService(Ci.nsIMicrosummaryService);
285
 
    } catch (e) {
286
 
      ms = null;
287
 
      this._log.warn("Could not load microsummary service");
288
 
      this._log.debug(e);
289
 
    }
290
 
    this.__defineGetter__("_ms", function() ms);
291
 
    return ms;
292
 
  },
293
 
 
294
 
  __ts: null,
295
 
  get _ts() {
296
 
    if (!this.__ts)
297
 
      this.__ts = Cc["@mozilla.org/browser/tagging-service;1"].
298
 
                  getService(Ci.nsITaggingService);
299
 
    return this.__ts;
300
 
  },
301
 
 
302
 
 
303
 
  itemExists: function BStore_itemExists(id) {
304
 
    return idForGUID(id) > 0;
305
 
  },
306
 
 
307
 
  // Hash of old GUIDs to the new renamed GUIDs
308
 
  aliases: {},
309
 
 
310
 
  applyIncoming: function BStore_applyIncoming(record) {
311
 
    // Ignore (accidental?) root changes
312
 
    if (record.id in kSpecialIds) {
313
 
      this._log.debug("Skipping change to root node: " + record.id);
314
 
      return;
315
 
    }
316
 
 
317
 
    // Convert GUID fields to the aliased GUID if necessary
318
 
    ["id", "parentid", "predecessorid"].forEach(function(field) {
319
 
      let alias = this.aliases[record[field]];
320
 
      if (alias != null)
321
 
        record[field] = alias;
322
 
    }, this);
323
 
 
324
 
    // Preprocess the record before doing the normal apply
325
 
    switch (record.type) {
326
 
      case "query": {
327
 
        // Convert the query uri if necessary
328
 
        if (record.bmkUri == null || record.folderName == null)
329
 
          break;
330
 
 
331
 
        // Tag something so that the tag exists
332
 
        let tag = record.folderName;
333
 
        let dummyURI = Utils.makeURI("about:weave#BStore_preprocess");
334
 
        this._ts.tagURI(dummyURI, [tag]);
335
 
 
336
 
        // Look for the id of the tag (that might have just been added)
337
 
        let tags = this._getNode(this._bms.tagsFolder);
338
 
        if (!(tags instanceof Ci.nsINavHistoryQueryResultNode))
339
 
          break;
340
 
 
341
 
        tags.containerOpen = true;
342
 
        for (let i = 0; i < tags.childCount; i++) {
343
 
          let child = tags.getChild(i);
344
 
          // Found the tag, so fix up the query to use the right id
345
 
          if (child.title == tag) {
346
 
            this._log.debug("query folder: " + tag + " = " + child.itemId);
347
 
            record.bmkUri = record.bmkUri.replace(/([:&]folder=)\d+/, "$1" +
348
 
              child.itemId);
349
 
            break;
350
 
          }
351
 
        }
352
 
        break;
353
 
      }
354
 
    }
355
 
 
356
 
    // Figure out the local id of the parent GUID if available
357
 
    let parentGUID = record.parentid;
358
 
    record._orphan = false;
359
 
    if (parentGUID != null) {
360
 
      let parentId = idForGUID(parentGUID);
361
 
 
362
 
      // Default to unfiled if we don't have the parent yet
363
 
      if (parentId <= 0) {
364
 
        this._log.trace("Reparenting to unfiled until parent is synced");
365
 
        record._orphan = true;
366
 
        parentId = kSpecialIds.unfiled;
367
 
      }
368
 
 
369
 
      // Save the parent id for modifying the bookmark later
370
 
      record._parent = parentId;
371
 
    }
372
 
 
373
 
    // Default to append unless we're not an orphan with the predecessor
374
 
    let predGUID = record.predecessorid;
375
 
    record._insertPos = Svc.Bookmark.DEFAULT_INDEX;
376
 
    if (!record._orphan) {
377
 
      // No predecessor means it's the first item
378
 
      if (predGUID == null)
379
 
        record._insertPos = 0;
380
 
      else {
381
 
        // The insert position is one after the predecessor of the same parent
382
 
        let predId = idForGUID(predGUID);
383
 
        if (predId != -1 && this._getParentGUIDForId(predId) == parentGUID) {
384
 
          record._insertPos = Svc.Bookmark.getItemIndex(predId) + 1;
385
 
          record._predId = predId;
386
 
        }
387
 
        else
388
 
          this._log.trace("Appending to end until predecessor is synced");
389
 
      }
390
 
    }
391
 
 
392
 
    // Do the normal processing of incoming records
393
 
    Store.prototype.applyIncoming.apply(this, arguments);
394
 
 
395
 
    // Do some post-processing if we have an item
396
 
    let itemId = idForGUID(record.id);
397
 
    if (itemId > 0) {
398
 
      // Move any children that are looking for this folder as a parent
399
 
      if (record.type == "folder")
400
 
        this._reparentOrphans(itemId);
401
 
 
402
 
      // Create an annotation to remember that it needs a parent
403
 
      // XXX Work around Bug 510628 by prepending parenT
404
 
      if (record._orphan)
405
 
        Utils.anno(itemId, PARENT_ANNO, "T" + parentGUID);
406
 
      // It's now in the right folder, so move annotated items behind this
407
 
      else
408
 
        this._attachFollowers(itemId);
409
 
 
410
 
      // Create an annotation if we have a predecessor but no position
411
 
      // XXX Work around Bug 510628 by prepending predecessoR
412
 
      if (predGUID != null && record._insertPos == Svc.Bookmark.DEFAULT_INDEX)
413
 
        Utils.anno(itemId, PREDECESSOR_ANNO, "R" + predGUID);
414
 
    }
415
 
  },
416
 
 
417
 
  /**
418
 
   * Find all ids of items that have a given value for an annotation
419
 
   */
420
 
  _findAnnoItems: function BStore__findAnnoItems(anno, val) {
421
 
    // XXX Work around Bug 510628 by prepending parenT
422
 
    if (anno == PARENT_ANNO)
423
 
      val = "T" + val;
424
 
    // XXX Work around Bug 510628 by prepending predecessoR
425
 
    else if (anno == PREDECESSOR_ANNO)
426
 
      val = "R" + val;
427
 
 
428
 
    return Svc.Annos.getItemsWithAnnotation(anno, {}).filter(function(id)
429
 
      Utils.anno(id, anno) == val);
430
 
  },
431
 
 
432
 
  /**
433
 
   * For the provided parent item, attach its children to it
434
 
   */
435
 
  _reparentOrphans: function _reparentOrphans(parentId) {
436
 
    // Find orphans and reunite with this folder parent
437
 
    let parentGUID = GUIDForId(parentId);
438
 
    let orphans = this._findAnnoItems(PARENT_ANNO, parentGUID);
439
 
 
440
 
    this._log.debug("Reparenting orphans " + orphans + " to " + parentId);
441
 
    orphans.forEach(function(orphan) {
442
 
      // Append the orphan under the parent unless it's supposed to be first
443
 
      let insertPos = Svc.Bookmark.DEFAULT_INDEX;
444
 
      if (!Svc.Annos.itemHasAnnotation(orphan, PREDECESSOR_ANNO))
445
 
        insertPos = 0;
446
 
 
447
 
      // Move the orphan to the parent and drop the missing parent annotation
448
 
      Svc.Bookmark.moveItem(orphan, parentId, insertPos);
449
 
      Svc.Annos.removeItemAnnotation(orphan, PARENT_ANNO);
450
 
    });
451
 
 
452
 
    // Fix up the ordering of the now-parented items
453
 
    orphans.forEach(this._attachFollowers, this);
454
 
  },
455
 
 
456
 
  /**
457
 
   * Move an item and all of its followers to a new position until reaching an
458
 
   * item that shouldn't be moved
459
 
   */
460
 
  _moveItemChain: function BStore__moveItemChain(itemId, insertPos, stopId) {
461
 
    let parentId = Svc.Bookmark.getFolderIdForItem(itemId);
462
 
 
463
 
    // Keep processing the item chain until it loops to the stop item
464
 
    do {
465
 
      // Figure out what's next in the chain
466
 
      let itemPos = Svc.Bookmark.getItemIndex(itemId);
467
 
      let nextId = Svc.Bookmark.getIdForItemAt(parentId, itemPos + 1);
468
 
 
469
 
      Svc.Bookmark.moveItem(itemId, parentId, insertPos);
470
 
      this._log.trace("Moved " + itemId + " to " + insertPos);
471
 
 
472
 
      // Prepare for the next item in the chain
473
 
      insertPos = Svc.Bookmark.getItemIndex(itemId) + 1;
474
 
      itemId = nextId;
475
 
 
476
 
      // Stop if we ran off the end or the item is looking for its pred.
477
 
      if (itemId == -1 || Svc.Annos.itemHasAnnotation(itemId, PREDECESSOR_ANNO))
478
 
        break;
479
 
    } while (itemId != stopId);
480
 
  },
481
 
 
482
 
  /**
483
 
   * For the provided predecessor item, attach its followers to it
484
 
   */
485
 
  _attachFollowers: function BStore__attachFollowers(predId) {
486
 
    let predGUID = GUIDForId(predId);
487
 
    let followers = this._findAnnoItems(PREDECESSOR_ANNO, predGUID);
488
 
    if (followers.length > 1)
489
 
      this._log.warn(predId + " has more than one followers: " + followers);
490
 
 
491
 
    // Start at the first follower and move the chain of followers
492
 
    let parent = Svc.Bookmark.getFolderIdForItem(predId);
493
 
    followers.forEach(function(follow) {
494
 
      this._log.trace("Repositioning " + follow + " behind " + predId);
495
 
      if (Svc.Bookmark.getFolderIdForItem(follow) != parent) {
496
 
        this._log.warn("Follower doesn't have the same parent: " + parent);
497
 
        return;
498
 
      }
499
 
 
500
 
      // Move the chain of followers to after the predecessor
501
 
      let insertPos = Svc.Bookmark.getItemIndex(predId) + 1;
502
 
      this._moveItemChain(follow, insertPos, predId);
503
 
 
504
 
      // Remove the annotation now that we're putting it in the right spot
505
 
      Svc.Annos.removeItemAnnotation(follow, PREDECESSOR_ANNO);
506
 
    }, this);
507
 
  },
508
 
 
509
 
  create: function BStore_create(record) {
510
 
    let newId;
511
 
    switch (record.type) {
512
 
    case "bookmark":
513
 
    case "query":
514
 
    case "microsummary": {
515
 
      let uri = Utils.makeURI(record.bmkUri);
516
 
      newId = this._bms.insertBookmark(record._parent, uri, record._insertPos,
517
 
        record.title);
518
 
      this._log.debug(["created bookmark", newId, "under", record._parent, "at",
519
 
        record._insertPos, "as", record.title, record.bmkUri].join(" "));
520
 
 
521
 
      this._tagURI(uri, record.tags);
522
 
      this._bms.setKeywordForBookmark(newId, record.keyword);
523
 
      if (record.description)
524
 
        Utils.anno(newId, "bookmarkProperties/description", record.description);
525
 
 
526
 
      if (record.loadInSidebar)
527
 
        Utils.anno(newId, "bookmarkProperties/loadInSidebar", true);
528
 
 
529
 
      if (record.type == "microsummary") {
530
 
        this._log.debug("   \-> is a microsummary");
531
 
        Utils.anno(newId, "bookmarks/staticTitle", record.staticTitle || "");
532
 
        let genURI = Utils.makeURI(record.generatorUri);
533
 
        if (this._ms) {
534
 
          try {
535
 
            let micsum = this._ms.createMicrosummary(uri, genURI);
536
 
            this._ms.setMicrosummary(newId, micsum);
537
 
          }
538
 
          catch(ex) { /* ignore "missing local generator" exceptions */ }
539
 
        }
540
 
        else
541
 
          this._log.warn("Can't create microsummary -- not supported.");
542
 
      }
543
 
    } break;
544
 
    case "folder":
545
 
      newId = this._bms.createFolder(record._parent, record.title,
546
 
        record._insertPos);
547
 
      this._log.debug(["created folder", newId, "under", record._parent, "at",
548
 
        record._insertPos, "as", record.title].join(" "));
549
 
 
550
 
      if (record.description)
551
 
        Utils.anno(newId, "bookmarkProperties/description", record.description);
552
 
      break;
553
 
    case "livemark":
554
 
      let siteURI = null;
555
 
      if (record.siteUri != null)
556
 
        siteURI = Utils.makeURI(record.siteUri);
557
 
 
558
 
      newId = this._ls.createLivemark(record._parent, record.title, siteURI,
559
 
        Utils.makeURI(record.feedUri), record._insertPos);
560
 
      this._log.debug(["created livemark", newId, "under", record._parent, "at",
561
 
        record._insertPos, "as", record.title, record.siteUri, record.feedUri].
562
 
        join(" "));
563
 
      break;
564
 
    case "separator":
565
 
      newId = this._bms.insertSeparator(record._parent, record._insertPos);
566
 
      this._log.debug(["created separator", newId, "under", record._parent,
567
 
        "at", record._insertPos].join(" "));
568
 
      break;
569
 
    case "item":
570
 
      this._log.debug(" -> got a generic places item.. do nothing?");
571
 
      return;
572
 
    default:
573
 
      this._log.error("_create: Unknown item type: " + record.type);
574
 
      return;
575
 
    }
576
 
 
577
 
    this._log.trace("Setting GUID of new item " + newId + " to " + record.id);
578
 
    this._setGUID(newId, record.id);
579
 
  },
580
 
 
581
 
  remove: function BStore_remove(record) {
582
 
    let itemId = idForGUID(record.id);
583
 
    if (itemId <= 0) {
584
 
      this._log.debug("Item " + record.id + " already removed");
585
 
      return;
586
 
    }
587
 
    var type = this._bms.getItemType(itemId);
588
 
 
589
 
    switch (type) {
590
 
    case this._bms.TYPE_BOOKMARK:
591
 
      this._log.debug("  -> removing bookmark " + record.id);
592
 
      this._ts.untagURI(this._bms.getBookmarkURI(itemId), null);
593
 
      this._bms.removeItem(itemId);
594
 
      break;
595
 
    case this._bms.TYPE_FOLDER:
596
 
      this._log.debug("  -> removing folder " + record.id);
597
 
      Svc.Bookmark.removeItem(itemId);
598
 
      break;
599
 
    case this._bms.TYPE_SEPARATOR:
600
 
      this._log.debug("  -> removing separator " + record.id);
601
 
      this._bms.removeItem(itemId);
602
 
      break;
603
 
    default:
604
 
      this._log.error("remove: Unknown item type: " + type);
605
 
      break;
606
 
    }
607
 
  },
608
 
 
609
 
  update: function BStore_update(record) {
610
 
    let itemId = idForGUID(record.id);
611
 
 
612
 
    if (itemId <= 0) {
613
 
      this._log.debug("Skipping update for unknown item: " + record.id);
614
 
      return;
615
 
    }
616
 
 
617
 
    this._log.trace("Updating " + record.id + " (" + itemId + ")");
618
 
 
619
 
    // Move the bookmark to a new parent if necessary
620
 
    if (Svc.Bookmark.getFolderIdForItem(itemId) != record._parent) {
621
 
      this._log.trace("Moving item to a new parent");
622
 
      Svc.Bookmark.moveItem(itemId, record._parent, record._insertPos);
623
 
    }
624
 
    // Move the chain of bookmarks to a new position
625
 
    else if (Svc.Bookmark.getItemIndex(itemId) != record._insertPos &&
626
 
             !record._orphan) {
627
 
      this._log.trace("Moving item and followers to a new position");
628
 
 
629
 
      // Stop moving at the predecessor unless we don't have one
630
 
      this._moveItemChain(itemId, record._insertPos, record._predId || itemId);
631
 
    }
632
 
 
633
 
    for (let [key, val] in Iterator(record.cleartext)) {
634
 
      switch (key) {
635
 
      case "title":
636
 
        this._bms.setItemTitle(itemId, val);
637
 
        break;
638
 
      case "bmkUri":
639
 
        this._bms.changeBookmarkURI(itemId, Utils.makeURI(val));
640
 
        break;
641
 
      case "tags":
642
 
        this._tagURI(this._bms.getBookmarkURI(itemId), val);
643
 
        break;
644
 
      case "keyword":
645
 
        this._bms.setKeywordForBookmark(itemId, val);
646
 
        break;
647
 
      case "description":
648
 
        Utils.anno(itemId, "bookmarkProperties/description", val);
649
 
        break;
650
 
      case "loadInSidebar":
651
 
        if (val)
652
 
          Utils.anno(itemId, "bookmarkProperties/loadInSidebar", true);
653
 
        else
654
 
          Svc.Annos.removeItemAnnotation(itemId, "bookmarkProperties/loadInSidebar");
655
 
        break;
656
 
      case "generatorUri": {
657
 
        try {
658
 
          let micsumURI = this._bms.getBookmarkURI(itemId);
659
 
          let genURI = Utils.makeURI(val);
660
 
          if (this._ms == SERVICE_NOT_SUPPORTED)
661
 
            this._log.warn("Can't create microsummary -- not supported.");
662
 
          else {
663
 
            let micsum = this._ms.createMicrosummary(micsumURI, genURI);
664
 
            this._ms.setMicrosummary(itemId, micsum);
665
 
          }
666
 
        } catch (e) {
667
 
          this._log.debug("Could not set microsummary generator URI: " + e);
668
 
        }
669
 
      } break;
670
 
      case "siteUri":
671
 
        this._ls.setSiteURI(itemId, Utils.makeURI(val));
672
 
        break;
673
 
      case "feedUri":
674
 
        this._ls.setFeedURI(itemId, Utils.makeURI(val));
675
 
        break;
676
 
      }
677
 
    }
678
 
  },
679
 
 
680
 
  changeItemID: function BStore_changeItemID(oldID, newID) {
681
 
    // Remember the GUID change for incoming records
682
 
    this.aliases[oldID] = newID;
683
 
 
684
 
    // Update any existing annotation references
685
 
    this._findAnnoItems(PARENT_ANNO, oldID).forEach(function(itemId) {
686
 
      Utils.anno(itemId, PARENT_ANNO, "T" + newID);
687
 
    }, this);
688
 
    this._findAnnoItems(PREDECESSOR_ANNO, oldID).forEach(function(itemId) {
689
 
      Utils.anno(itemId, PREDECESSOR_ANNO, "R" + newID);
690
 
    }, this);
691
 
 
692
 
    // Make sure there's an item to change GUIDs
693
 
    let itemId = idForGUID(oldID);
694
 
    if (itemId <= 0)
695
 
      return;
696
 
 
697
 
    this._log.debug("Changing GUID " + oldID + " to " + newID);
698
 
    this._setGUID(itemId, newID);
699
 
  },
700
 
 
701
 
  _setGUID: function BStore__setGUID(itemId, guid) {
702
 
    let collision = idForGUID(guid);
703
 
    if (collision != -1) {
704
 
      this._log.warn("Freeing up GUID " + guid  + " used by " + collision);
705
 
      Svc.Annos.removeItemAnnotation(collision, "placesInternal/GUID");
706
 
    }
707
 
    Svc.Bookmark.setItemGUID(itemId, guid);
708
 
  },
709
 
 
710
 
  _getNode: function BStore__getNode(folder) {
711
 
    let query = this._hsvc.getNewQuery();
712
 
    query.setFolders([folder], 1);
713
 
    return this._hsvc.executeQuery(query, this._hsvc.getNewQueryOptions()).root;
714
 
  },
715
 
 
716
 
  _getTags: function BStore__getTags(uri) {
717
 
    try {
718
 
      if (typeof(uri) == "string")
719
 
        uri = Utils.makeURI(uri);
720
 
    } catch(e) {
721
 
      this._log.warn("Could not parse URI \"" + uri + "\": " + e);
722
 
    }
723
 
    return this._ts.getTagsForURI(uri, {});
724
 
  },
725
 
 
726
 
  _getDescription: function BStore__getDescription(id) {
727
 
    try {
728
 
      return Utils.anno(id, "bookmarkProperties/description");
729
 
    } catch (e) {
730
 
      return undefined;
731
 
    }
732
 
  },
733
 
 
734
 
  _isLoadInSidebar: function BStore__isLoadInSidebar(id) {
735
 
    return Svc.Annos.itemHasAnnotation(id, "bookmarkProperties/loadInSidebar");
736
 
  },
737
 
 
738
 
  _getStaticTitle: function BStore__getStaticTitle(id) {
739
 
    try {
740
 
      return Utils.anno(id, "bookmarks/staticTitle");
741
 
    } catch (e) {
742
 
      return "";
743
 
    }
744
 
  },
745
 
 
746
 
  // Create a record starting from the weave id (places guid)
747
 
  createRecord: function createRecord(guid) {
748
 
    let placeId = idForGUID(guid);
749
 
    let record;
750
 
    if (placeId <= 0) { // deleted item
751
 
      record = new PlacesItem();
752
 
      record.deleted = true;
753
 
      return record;
754
 
    }
755
 
 
756
 
    let parent = Svc.Bookmark.getFolderIdForItem(placeId);
757
 
    switch (this._bms.getItemType(placeId)) {
758
 
    case this._bms.TYPE_BOOKMARK:
759
 
      let bmkUri = this._bms.getBookmarkURI(placeId).spec;
760
 
      if (this._ms && this._ms.hasMicrosummary(placeId)) {
761
 
        record = new BookmarkMicsum();
762
 
        let micsum = this._ms.getMicrosummary(placeId);
763
 
        record.generatorUri = micsum.generator.uri.spec; // breaks local generators
764
 
        record.staticTitle = this._getStaticTitle(placeId);
765
 
      }
766
 
      else {
767
 
        if (bmkUri.search(/^place:/) == 0) {
768
 
          record = new BookmarkQuery();
769
 
 
770
 
          // Get the actual tag name instead of the local itemId
771
 
          let folder = bmkUri.match(/[:&]folder=(\d+)/);
772
 
          try {
773
 
            // There might not be the tag yet when creating on a new client
774
 
            if (folder != null) {
775
 
              folder = folder[1];
776
 
              record.folderName = this._bms.getItemTitle(folder);
777
 
              this._log.debug("query id: " + folder + " = " + record.folderName);
778
 
            }
779
 
          }
780
 
          catch(ex) {}
781
 
        }
782
 
        else
783
 
          record = new Bookmark();
784
 
        record.title = this._bms.getItemTitle(placeId);
785
 
      }
786
 
 
787
 
      record.parentName = Svc.Bookmark.getItemTitle(parent);
788
 
      record.bmkUri = bmkUri;
789
 
      record.tags = this._getTags(record.bmkUri);
790
 
      record.keyword = this._bms.getKeywordForBookmark(placeId);
791
 
      record.description = this._getDescription(placeId);
792
 
      record.loadInSidebar = this._isLoadInSidebar(placeId);
793
 
      break;
794
 
 
795
 
    case this._bms.TYPE_FOLDER:
796
 
      if (this._ls.isLivemark(placeId)) {
797
 
        record = new Livemark();
798
 
 
799
 
        let siteURI = this._ls.getSiteURI(placeId);
800
 
        if (siteURI != null)
801
 
          record.siteUri = siteURI.spec;
802
 
        record.feedUri = this._ls.getFeedURI(placeId).spec;
803
 
 
804
 
      } else {
805
 
        record = new BookmarkFolder();
806
 
      }
807
 
 
808
 
      record.parentName = Svc.Bookmark.getItemTitle(parent);
809
 
      record.title = this._bms.getItemTitle(placeId);
810
 
      record.description = this._getDescription(placeId);
811
 
      break;
812
 
 
813
 
    case this._bms.TYPE_SEPARATOR:
814
 
      record = new BookmarkSeparator();
815
 
      // Create a positioning identifier for the separator
816
 
      record.parentName = Svc.Bookmark.getItemTitle(parent);
817
 
      record.pos = Svc.Bookmark.getItemIndex(placeId);
818
 
      break;
819
 
 
820
 
    case this._bms.TYPE_DYNAMIC_CONTAINER:
821
 
      record = new PlacesItem();
822
 
      this._log.warn("Don't know how to serialize dynamic containers yet");
823
 
      break;
824
 
 
825
 
    default:
826
 
      record = new PlacesItem();
827
 
      this._log.warn("Unknown item type, cannot serialize: " +
828
 
                     this._bms.getItemType(placeId));
829
 
    }
830
 
 
831
 
    record.parentid = this._getParentGUIDForId(placeId);
832
 
    record.predecessorid = this._getPredecessorGUIDForId(placeId);
833
 
    record.sortindex = this._calculateIndex(record);
834
 
 
835
 
    return record;
836
 
  },
837
 
 
838
 
  get _frecencyStm() {
839
 
    this._log.trace("Creating SQL statement: _frecencyStm");
840
 
    let stm = Svc.History.DBConnection.createStatement(
841
 
      "SELECT frecency " +
842
 
      "FROM moz_places " +
843
 
      "WHERE url = :url");
844
 
    this.__defineGetter__("_frecencyStm", function() stm);
845
 
    return stm;
846
 
  },
847
 
 
848
 
  _calculateIndex: function _calculateIndex(record) {
849
 
    // For anything directly under the toolbar, give it a boost of more than an
850
 
    // unvisited bookmark
851
 
    let index = 0;
852
 
    if (record.parentid == "toolbar")
853
 
      index += 150;
854
 
 
855
 
    // Add in the bookmark's frecency if we have something
856
 
    if (record.bmkUri != null) {
857
 
      try {
858
 
        this._frecencyStm.params.url = record.bmkUri;
859
 
        if (this._frecencyStm.step())
860
 
          index += this._frecencyStm.row.frecency;
861
 
      }
862
 
      finally {
863
 
        this._frecencyStm.reset();
864
 
      }
865
 
    }
866
 
 
867
 
    return index;
868
 
  },
869
 
 
870
 
  _getParentGUIDForId: function BStore__getParentGUIDForId(itemId) {
871
 
    // Give the parent annotation if it exists
872
 
    try {
873
 
      // XXX Work around Bug 510628 by removing prepended parenT
874
 
      return Utils.anno(itemId, PARENT_ANNO).slice(1);
875
 
    }
876
 
    catch(ex) {}
877
 
 
878
 
    let parentid = this._bms.getFolderIdForItem(itemId);
879
 
    if (parentid == -1) {
880
 
      this._log.debug("Found orphan bookmark, reparenting to unfiled");
881
 
      parentid = this._bms.unfiledBookmarksFolder;
882
 
      this._bms.moveItem(itemId, parentid, -1);
883
 
    }
884
 
    return GUIDForId(parentid);
885
 
  },
886
 
 
887
 
  _getPredecessorGUIDForId: function BStore__getPredecessorGUIDForId(itemId) {
888
 
    // Give the predecessor annotation if it exists
889
 
    try {
890
 
      // XXX Work around Bug 510628 by removing prepended predecessoR
891
 
      return Utils.anno(itemId, PREDECESSOR_ANNO).slice(1);
892
 
    }
893
 
    catch(ex) {}
894
 
 
895
 
    // Figure out the predecessor, unless it's the first item
896
 
    let itemPos = Svc.Bookmark.getItemIndex(itemId);
897
 
    if (itemPos == 0)
898
 
      return;
899
 
 
900
 
    // For items directly under unfiled/unsorted, give no predecessor
901
 
    let parentId = Svc.Bookmark.getFolderIdForItem(itemId);
902
 
    if (parentId == Svc.Bookmark.unfiledBookmarksFolder)
903
 
      return;
904
 
 
905
 
    let predecessorId = Svc.Bookmark.getIdForItemAt(parentId, itemPos - 1);
906
 
    if (predecessorId == -1) {
907
 
      this._log.debug("No predecessor directly before " + itemId + " under " +
908
 
        parentId + " at " + itemPos);
909
 
 
910
 
      // Find the predecessor before the item
911
 
      do {
912
 
        // No more items to check, it must be the first one
913
 
        if (--itemPos < 0)
914
 
          break;
915
 
        predecessorId = Svc.Bookmark.getIdForItemAt(parentId, itemPos);
916
 
      } while (predecessorId == -1);
917
 
 
918
 
      // Fix up the item to be at the right position for next time
919
 
      itemPos++;
920
 
      this._log.debug("Fixing " + itemId + " to be at position " + itemPos);
921
 
      Svc.Bookmark.moveItem(itemId, parentId, itemPos);
922
 
 
923
 
      // There must be no predecessor for this item!
924
 
      if (itemPos == 0)
925
 
        return;
926
 
    }
927
 
 
928
 
    return GUIDForId(predecessorId);
929
 
  },
930
 
 
931
 
  _getChildren: function BStore_getChildren(guid, items) {
932
 
    let node = guid; // the recursion case
933
 
    if (typeof(node) == "string") // callers will give us the guid as the first arg
934
 
      node = this._getNode(idForGUID(guid));
935
 
 
936
 
    if (node.type == node.RESULT_TYPE_FOLDER &&
937
 
        !this._ls.isLivemark(node.itemId)) {
938
 
      node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
939
 
      node.containerOpen = true;
940
 
 
941
 
      // Remember all the children GUIDs and recursively get more
942
 
      for (var i = 0; i < node.childCount; i++) {
943
 
        let child = node.getChild(i);
944
 
        items[GUIDForId(child.itemId)] = true;
945
 
        this._getChildren(child, items);
946
 
      }
947
 
    }
948
 
 
949
 
    return items;
950
 
  },
951
 
 
952
 
  _tagURI: function BStore_tagURI(bmkURI, tags) {
953
 
    // Filter out any null/undefined/empty tags
954
 
    tags = tags.filter(function(t) t);
955
 
 
956
 
    // Temporarily tag a dummy uri to preserve tag ids when untagging
957
 
    let dummyURI = Utils.makeURI("about:weave#BStore_tagURI");
958
 
    this._ts.tagURI(dummyURI, tags);
959
 
    this._ts.untagURI(bmkURI, null);
960
 
    this._ts.tagURI(bmkURI, tags);
961
 
    this._ts.untagURI(dummyURI, null);
962
 
  },
963
 
 
964
 
  getAllIDs: function BStore_getAllIDs() {
965
 
    let items = {};
966
 
    for (let [guid, id] in Iterator(kSpecialIds))
967
 
      if (guid != "places" && guid != "tags")
968
 
        this._getChildren(guid, items);
969
 
    return items;
970
 
  },
971
 
 
972
 
  wipe: function BStore_wipe() {
973
 
    // Save a backup before clearing out all bookmarks
974
 
    archiveBookmarks();
975
 
 
976
 
    for (let [guid, id] in Iterator(kSpecialIds))
977
 
      if (guid != "places")
978
 
        this._bms.removeFolderChildren(id);
979
 
  }
980
 
};
981
 
 
982
 
function BookmarksTracker(name) {
983
 
  Tracker.call(this, name);
984
 
 
985
 
  // Ignore changes to the special roots
986
 
  for (let guid in kSpecialIds)
987
 
    this.ignoreID(guid);
988
 
 
989
 
  Svc.Bookmark.addObserver(this, false);
990
 
}
991
 
BookmarksTracker.prototype = {
992
 
  __proto__: Tracker.prototype,
993
 
 
994
 
  get _bms() {
995
 
    let bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
996
 
      getService(Ci.nsINavBookmarksService);
997
 
    this.__defineGetter__("_bms", function() bms);
998
 
    return bms;
999
 
  },
1000
 
 
1001
 
  get _ls() {
1002
 
    let ls = Cc["@mozilla.org/browser/livemark-service;2"].
1003
 
      getService(Ci.nsILivemarkService);
1004
 
    this.__defineGetter__("_ls", function() ls);
1005
 
    return ls;
1006
 
  },
1007
 
 
1008
 
  QueryInterface: XPCOMUtils.generateQI([
1009
 
    Ci.nsINavBookmarkObserver,
1010
 
    Ci.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS
1011
 
  ]),
1012
 
 
1013
 
  /**
1014
 
   * Add a bookmark (places) id to be uploaded and bump up the sync score
1015
 
   *
1016
 
   * @param itemId
1017
 
   *        Places internal id of the bookmark to upload
1018
 
   */
1019
 
  _addId: function BMT__addId(itemId) {
1020
 
    if (this.addChangedID(GUIDForId(itemId)))
1021
 
      this._upScore();
1022
 
  },
1023
 
 
1024
 
  /**
1025
 
   * Add the successor id for the item that follows the given item
1026
 
   */
1027
 
  _addSuccessor: function BMT__addSuccessor(itemId) {
1028
 
    let parentId = Svc.Bookmark.getFolderIdForItem(itemId);
1029
 
    let itemPos = Svc.Bookmark.getItemIndex(itemId);
1030
 
    let succId = Svc.Bookmark.getIdForItemAt(parentId, itemPos + 1);
1031
 
    if (succId != -1)
1032
 
      this._addId(succId);
1033
 
  },
1034
 
 
1035
 
  /* Every add/remove/change is worth 10 points */
1036
 
  _upScore: function BMT__upScore() {
1037
 
    this.score += 10;
1038
 
  },
1039
 
 
1040
 
  /**
1041
 
   * Determine if a change should be ignored: we're ignoring everything or the
1042
 
   * folder is for livemarks
1043
 
   *
1044
 
   * @param itemId
1045
 
   *        Item under consideration to ignore
1046
 
   * @param folder (optional)
1047
 
   *        Folder of the item being changed
1048
 
   */
1049
 
  _ignore: function BMT__ignore(itemId, folder) {
1050
 
    // Ignore unconditionally if the engine tells us to
1051
 
    if (this.ignoreAll)
1052
 
      return true;
1053
 
 
1054
 
    // Ensure that the mobile bookmarks query is correct in the UI
1055
 
    this._ensureMobileQuery();
1056
 
 
1057
 
    // Make sure to remove items that have the exclude annotation
1058
 
    if (Svc.Annos.itemHasAnnotation(itemId, "places/excludeFromBackup")) {
1059
 
      this.removeChangedID(GUIDForId(itemId));
1060
 
      return true;
1061
 
    }
1062
 
 
1063
 
    // Get the folder id if we weren't given one
1064
 
    if (folder == null)
1065
 
      folder = this._bms.getFolderIdForItem(itemId);
1066
 
 
1067
 
    let tags = kSpecialIds.tags;
1068
 
    // Ignore changes to tags (folders under the tags folder)
1069
 
    if (folder == tags)
1070
 
      return true;
1071
 
 
1072
 
    // Ignore tag items (the actual instance of a tag for a bookmark)
1073
 
    if (this._bms.getFolderIdForItem(folder) == tags)
1074
 
      return true;
1075
 
 
1076
 
    // Ignore livemark children
1077
 
    return this._ls.isLivemark(folder);
1078
 
  },
1079
 
 
1080
 
  onItemAdded: function BMT_onEndUpdateBatch(itemId, folder, index) {
1081
 
    if (this._ignore(itemId, folder))
1082
 
      return;
1083
 
 
1084
 
    this._log.trace("onItemAdded: " + itemId);
1085
 
    this._addId(itemId);
1086
 
    this._addSuccessor(itemId);
1087
 
  },
1088
 
 
1089
 
  onBeforeItemRemoved: function BMT_onBeforeItemRemoved(itemId) {
1090
 
    if (this._ignore(itemId))
1091
 
      return;
1092
 
 
1093
 
    this._log.trace("onBeforeItemRemoved: " + itemId);
1094
 
    this._addId(itemId);
1095
 
    this._addSuccessor(itemId);
1096
 
  },
1097
 
 
1098
 
  _ensureMobileQuery: function _ensureMobileQuery() {
1099
 
    let anno = "PlacesOrganizer/OrganizerQuery";
1100
 
    let find = function(val) Svc.Annos.getItemsWithAnnotation(anno, {}).filter(
1101
 
      function(id) Utils.anno(id, anno) == val);
1102
 
 
1103
 
    // Don't continue if the Library isn't ready
1104
 
    let all = find("AllBookmarks");
1105
 
    if (all.length == 0)
1106
 
      return;
1107
 
 
1108
 
    // Disable handling of notifications while changing the mobile query
1109
 
    this.ignoreAll = true;
1110
 
 
1111
 
    let mobile = find("MobileBookmarks");
1112
 
    let queryURI = Utils.makeURI("place:folder=" + kSpecialIds.mobile);
1113
 
    let title = Str.sync.get("mobile.label");
1114
 
 
1115
 
    // Don't add OR do remove the mobile bookmarks if there's nothing
1116
 
    if (Svc.Bookmark.getIdForItemAt(kSpecialIds.mobile, 0) == -1) {
1117
 
      if (mobile.length != 0)
1118
 
        Svc.Bookmark.removeItem(mobile[0]);
1119
 
    }
1120
 
    // Add the mobile bookmarks query if it doesn't exist
1121
 
    else if (mobile.length == 0) {
1122
 
      let query = Svc.Bookmark.insertBookmark(all[0], queryURI, -1, title);
1123
 
      Utils.anno(query, anno, "MobileBookmarks");
1124
 
      Utils.anno(query, "places/excludeFromBackup", 1);
1125
 
    }
1126
 
    // Make sure the existing title is correct
1127
 
    else if (Svc.Bookmark.getItemTitle(mobile[0]) != title)
1128
 
      Svc.Bookmark.setItemTitle(mobile[0], title);
1129
 
 
1130
 
    this.ignoreAll = false;
1131
 
  },
1132
 
 
1133
 
  onItemChanged: function BMT_onItemChanged(itemId, property, isAnno, value) {
1134
 
    if (this._ignore(itemId))
1135
 
      return;
1136
 
 
1137
 
    // ignore annotations except for the ones that we sync
1138
 
    let annos = ["bookmarkProperties/description",
1139
 
      "bookmarkProperties/loadInSidebar", "bookmarks/staticTitle",
1140
 
      "livemark/feedURI", "livemark/siteURI", "microsummary/generatorURI"];
1141
 
    if (isAnno && annos.indexOf(property) == -1)
1142
 
      return;
1143
 
 
1144
 
    // Ignore favicon changes to avoid unnecessary churn
1145
 
    if (property == "favicon")
1146
 
      return;
1147
 
 
1148
 
    this._log.trace("onItemChanged: " + itemId +
1149
 
                    (", " + property + (isAnno? " (anno)" : "")) +
1150
 
                    (value? (" = \"" + value + "\"") : ""));
1151
 
    this._addId(itemId);
1152
 
  },
1153
 
 
1154
 
  onItemMoved: function BMT_onItemMoved(itemId, oldParent, oldIndex, newParent, newIndex) {
1155
 
    if (this._ignore(itemId))
1156
 
      return;
1157
 
 
1158
 
    this._log.trace("onItemMoved: " + itemId);
1159
 
    this._addId(itemId);
1160
 
    this._addSuccessor(itemId);
1161
 
 
1162
 
    // Get the thing that's now at the old place
1163
 
    let oldSucc = Svc.Bookmark.getIdForItemAt(oldParent, oldIndex);
1164
 
    if (oldSucc != -1)
1165
 
      this._addId(oldSucc);
1166
 
 
1167
 
    // Remove any position annotations now that the user moved the item
1168
 
    Svc.Annos.removeItemAnnotation(itemId, PARENT_ANNO);
1169
 
    Svc.Annos.removeItemAnnotation(itemId, PREDECESSOR_ANNO);
1170
 
  },
1171
 
 
1172
 
  onBeginUpdateBatch: function BMT_onBeginUpdateBatch() {},
1173
 
  onEndUpdateBatch: function BMT_onEndUpdateBatch() {},
1174
 
  onItemRemoved: function BMT_onItemRemoved(itemId, folder, index) {},
1175
 
  onItemVisited: function BMT_onItemVisited(itemId, aVisitID, time) {}
1176
 
};